C++ 我想这样用(六)

时间:2024-06-06 16:36:32

嗯,上一篇已经介绍了面向过程编程的语法知识,接下来是最后的也是最重要的一个部分:

第三部分:基于对象的编程风格

1.构造函数的两种写法

比如我们有如下的类定义:

class Circle

{

public:

  Circle(float r);

private:

  float radius;

};

上面我们只是声明了构造函数,接下来要具体定义它。第一种写法很常见,也好理解,大家通常都是这么写的:

Circle::Circle(float r) { radius = r }

但是除了在函数体内部对类成员赋值的写法外,还有一种写法:类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表)... { 类的初始化 }。而且据说使用初始化列表比使用赋值语句的效率要高。 于是上面的构造函数可以写成如下形式:

Circle::Circle(float r):radius(r){}

恩,其实我还是比较喜欢第一种,然而有时候却必须用第二种:类成员有const(因为const成员只能被初始化,不能被赋值)和引用数据成员只能用初始化写法(第二种),不能被赋值写法(第一种)。

2.构造函数的重载

在一个类中可以定义多个构造函数,以便提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。这称为构造函数的重载。 接下来看一个例子吧:

class Box

{

public :

  //定义一个无参的构造函数

  Box(){ height=10; width=10; length=10; };

  //定义一个有参的构造函数,用参数的初始化表对数据成员初始化

  Box(int h,int w,int len):height(h),width(w),length(len){ }

private :

  int height;  //高度

  int width;   //宽度

  int length;  //长度

}

好吧,这个例子是我网上找来的,而且我个人认为这是错误的示范,绝对的错误,这根本不是重载的正确用法,我觉得这种情况应该用默认参数技术!!那么什么时候才该使用构造函数的重载呢,你可以看看“C++ 我想这样用(五) ”中的重载例子。那个例子里的类才是真正需要重载构造函数的。

3.要不要析构函数

析构函数是与构造函数作用相反的函数。析构函数在下边3种情况时被调用:

a.对象生命周期结束,被销毁时;

b.delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;

c.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。

通常如果你的类成员都是一些“值类型”,换言之你的类成员都是“储值”的类型,如int char等,生存期过了就会被回收,那么你不需要写析构函数。反之如果你的类成员中有“储地址”的类型,比如指向new出来的空间的指针,那么你可一定要记得写析构函数,而且妥当的delete掉你new出来的空间。否则就引起了内存泄漏了。

class MyClass

{

private:

  char * _pName;

public:

  //这是你的构造函数

  MyClass(const char *name)

  {

    if (name)  {   _pName=new char[strlen(name)];  ......     };

  };

  //这时需要自己写出析构函数

~MyClass()

  {

    if (_pName)            delete [] pName;

  };

};

4.类的复制及四个默认函数

有人说,我没有写构造和析构函数啊,为甚还是可以正常的创建和销毁一个对象啊?嗯,好吧,我只能说编译器再一次给你擦屁股了。。。。。。 其实空类什么都不会有,所谓的默认构造函数、默认复制(拷贝)构造函数、默认赋值符操作符、默认析构函数是不会产生的,只有当用到这些函数或操作符时,编译器才会为我们产生所需要的函数或操作符。但是编译器产生的是什么特殊功能都没有的函数,为的只是让我们的程序能编译通过,如果你想要自己实现什么功能,如上面说的构造和析构,请自己实现吧~~~

构造和析构已经说过了,然而类中还有一个比较常用的功能就是复制,下面就说说和“复制”相关的拷贝构造函数和赋值符函数吧:

Triangular tri1(8);

Triangular tri2 = tri1;  //写法一

Triangular tri2(tri1);   //写法二

上述两种写法的形式虽然不同,但是意义是一样的,都是用已经存在的对象来创建新的对象,使两者内容一致。类不是int,怎么说复制就复制了呢?哪有那么容易呢?没错,我们能这样做是因为编译器为我们生成了默认的拷贝构造函数:

Triangular::Triangular(const Triangular& tri);

该默认函数是以成员逐一初始化的方式执行的,即类成员逐个复制。然而这种方法显然并不适合所有情况,如果我们的类成员有指针型的呢?没错,我们就只复制了地址,没有复制对应的空间,我们通常想要的是完整的拷贝,拷贝之后的tri1和tri2应该是互相无关的。这时我们需要自己来完成拷贝构造函数的编写。注意拷贝构造函数是参数唯一且固定的构造函数,所以没有返回值。在C++语言里:String   s2(s1);  和  String   s3   =   s1; 只是语法形式的不同,意义是一样的,都是定义加初始化,都调用拷贝构造函数。

除了上面这种完整的彻底的复制(深复制),我们有时仅仅需要简单的引用,即浅复制:

Triangular tri1(8);

Triangular tri2 ;

tri2 = tri1;   //这里完全不同与上面的写法一

这种情况下,我们已经有了两个存在了的对象,要做的仅仅是让tri2简单的与tri1有相同的引用。这时我们用到了赋值符函数:

Triangular & Triangular::operator = (const Triangular& RightSides)
{
    nSize=RightSides.nSize; //复制常规成员
    char *temp=new char[nSize]; //复制指针指向的内容 
    memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
    delete []pBuffer; //删除原指针指向内容  (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
    pBuffer=temp;   //建立新指向
    return *this
}

和拷贝构造函数的实现不一样,operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。什么????你说没看懂上面的像函数的东西是个啥??好吧,我也一样看不懂,因为这已经涉及到操作符重载了,是很高深的哦,后面会说到。总之在这部分要记住三个比较关键的函数:构造函数,析构函数,以及拷贝构造函数。这不是C++所特有的东西,是面向对象的基本概念,一般的面向对象语言里都有。比如java和C#,只不过语法上大同小异而已。

5.类的静态成员和静态成员函数

声明为static的类成员或者成员函数便能在类的范围内共同享,我们把这样的成员称做静态成员和静态成员函数。静态方法就是与该类相关的,是类的一种行为,而不是与该类的实例对象相关。静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。同样静态成员可以被初始化,但也只能在类体外进行初始化。我在网上找来了一个例子,比较直观:

#include <iostream> 
using namespace std; 
 
class Internet 

public: 
    Internet(char *name,char *address) 
    { 
        strcpy(Internet::name,name); 
        strcpy(Internet::address,address); 
        count++; 
    } 
    static void Internet::Sc()//静态成员函数 
    { 
        cout<<count<<endl; 
    } 
    Internet &Rq(); 
public: 
    char name[20]; 
    char address[20]; 
    static int count;//这里如果写成static int count=0;就是错误的 
}; 
 
Internet& Internet::Rq()//返回引用的成员函数 

    return *this; 

 
int Internet::count = 0;//静态成员的初始化 
void vist() 

    Internet a1("中国软件开发实验室","www.cndev-lab.com"); 
    Internet a2("中国软件开发实验室","www.cndev-lab.com"); 

void fn(Internet &s) 

    cout<<s.Rq().count; 

void main() 

    cout<<Internet::count<<endl;//静态成员值的输出 
    vist(); 
    Internet::Sc();//静态成员函数的调用 
    Internet b("中国软件开发实验室","www.cndev-lab.com"); 
    Internet::Sc(); 
    fn(b); 
    cin.get(); 
}

一定要理解了静态成员或静态成员函数的实际意义,才去使用它们,不然就是毫无意义的了。这里我吐槽下python,一般的语言都是这种定义,静态的类方法和非静态的类方法,可是python里面却有三种东西:实例函数,类函数,静态函数。第一个很好理解,就是那种普通的“类的非静态函数”了,但是后面两个真的就有些暧昧不清了,我是有些糊涂的,是的,曾经搞清楚了,因为我不会3个同时用到,所以很快又混淆了,不知道哪位大大解释下这种特立独行的意义。