原文来自http://www.cplusplus.com/doc/tutorial/classes/。这里算是半翻译半解释,一半是原网站上的,一半是自己的理解,某人要C++期末考试了,算是帮ta吧。
先贴上Classes(I) 类(一)中类的声明吧:
class Rectangle { int width, height; public: void set_values(int ,int); int area(void){ return (width*height); } }; // 类结束有个分号;英文分号,别忘了在这个类中(Rectangle),我们修改类中的数据成员(data member)是通过调用公有访问属性的set_values函数实现的,获取对象的面积是通过rect.area()函数调用完成的。
但是这样一来,势必会出现另一个问题——如果忘记了先设置值(set_values),一上来,就是调用area()。这样会出现什么情况呢?
recta area: 1592440624这是在code::blocks中,而且是在我的机器中的一次运行结果。你会发现,输出的面积值非常大,为啥?因为没初始化。
为了防止类似现象发生,我们引入一个非常好听但是又非常难以理解的名词——构造函数(constructor)。
构造函数??!!
读点英语,帮你加深一点点的印象。。。
A class can include a special function called its constructor, which is automatically called whenever a new object of this class is created, allowing the class to initialize member variables or allocate storage.
一个类可以包含一个特别的函数:构造函数,当新的类对象被创建时,构造函数会被自动调用,允许类初始化成员变量或分配存储(空间)。
构造函数长得什么样子?它非常帅,而且与众不同,它没有返回值类型(甚至没有void)。它的名字和类名相同。
记住了吗?名字和类名相同,名字前面是光秃秃的,没有返回值类型,没有void,没有波浪线~。
class Rectangle { int width, height; public: Rectangle(int ,int); int area(void){ return (width*height); } };仔细看看上面的一段代码,找到了吗?没有返回值类型,函数名和类名相同。是的,
Rectangle(int ,int); // 构造函数声明部分这里,我们对之前的类做了什么?我们将set_values函数改成了Rectangle函数(构造函数)。与set_values函数不同的是,Rectangle构造函数没有返回值类型。任何构造函数都没有返回值类型。
如果我们要对其进行定义,又该怎么做呢?
Rectangle::Rectangle(int x, int y){ width = x; height = y; }下面是原先的set_values函数头部分
void Rectangle::set_values(int x, int y)看到了吗?Rectangle::Rectangle(int x, int y){ .... } 函数定义中,第一个Rectangle告诉我们,这是Rectangle的一部分,它原本属于Rectangle类,只是被拿出来了。紧接着就是域解析运算符(或者域操作符 scope operator). 第二个Rectangle(双冒号后面的那个)是函数名,构造函数名。参数列表中有两个参数x和y,参数类型为int。要注意参数列表中参数数目以及参数类型要与类声明中的构造函数的参数数目以及参数类型要相同。
#include <iostream> using namespace std; class Rectangle { int width, height; public: Rectangle(int ,int); // 构造函数声明 int area(void){ return (width*height); } }; // 构造函数定义 Rectangle::Rectangle(int x, int y){ width = x; height = y; } int main() { Rectangle recta(3,4), rectb(4,5); cout<<"recta area: "<<recta.area()<<endl; cout<<"rectb area: "<<rectb.area()<<endl; return 0; }程序输出:
recta area: 12 rectb area: 20这样,就不再需要调用set_values函数,创建类对象的时候就传入参数Rectangle recta(3,4)。当程序执行到这一句的时候,它会自动调用类的构造函数,传入的参数为两个参数3和4。与类中的构造函数Rectangle(int ,int )匹配成功。这样,就开始执行构造函数函数体中的赋值语句,width的值等于x的值(3),height的值等于y的值(4)。类数据成员初始化就此完成。
要注意,虽然构造函数也是成员函数,但是你不能像调用普通成员函数那样调用构造函数。如rect.Rectangle(3,4)。它只在新的类对象创建的时候执行,而且仅此一次。并且是自动调用的。
Notice how neither the constructor prototype declaration(within the class) nor the latter constructor definition, have return values; not even void; Contructors never return values, they simply initialize the object.
要注意:不论是类里面的构造函数原型(构造函数声明),还是后面的构造函数定义,它们都没有返回值,甚至连个void都没有。构造函数从来不返回任何值,它们仅仅是初始化对象。
那么,问题来了:
如果在创建类对象的时候,没有给参数,或者参数数目不一致怎么办?如
Rectangle recta; Rectangle rectb(4);那我会告诉你,很不幸,编译器不会让你通过。它会报错:
no matching function for call to 'Rectangle::Rectangle()'|说是没有匹配到函数Rectangle::Rectangle()。与类名相同的函数Rectangle是类中的构造函数,也就是说,没有参数列表为空的构造函数。
即使是你把这句话修改了Rectangle recta; 修改为Rectangle recta(3,4);下一句也会编译不通过的。猜猜会报什么错误:
no matching function for call to 'Rectangle::Rectangle(int)'|就是说,类中没有只有一个int类型参数的构造函数。
我们该怎么办?可取的方法如下(写很多很多的构造函数,分别为:0个参数,1个参数,2个参数的构造函数):
#include <iostream> using namespace std; class Rectangle { int width, height; public: Rectangle(); // 构造函数声明 Rectangle(int); // 构造函数声明 Rectangle(int ,int); // 构造函数声明 int area(void){ return (width*height); } }; // 构造函数定义 Rectangle::Rectangle(){ width = 0; height = 0; } // 构造函数定义 Rectangle::Rectangle(int x){ width = x; height = 0; } // 构造函数定义 Rectangle::Rectangle(int x, int y){ width = x; height = y; } int main() { Rectangle recta; Rectangle rectb(3); Rectangle rectc(3,4); cout<<"recta area: "<<recta.area()<<endl; cout<<"rectb area: "<<rectb.area()<<endl; cout<<"rectc area: "<<rectc.area()<<endl; return 0; }程序输出:
recta area: 0 // 因为 x=y=0 rectb area: 0 // 因为 y=0 rectc area: 12在编写mai函数中的
Rectangle recta;时,我犯了一个错误,看似平凡其实问题很严重。我把Rectangle recta;写成了
Rectangle recta();为啥?因为惯性思维,一个参数是括号里面一个数,两个参数括号里面是两个整数。没有参数就是括号里面没有任何东西嘛,大错特错。
原文中是这样解释的:
This is because the empty set of parentheses would make of recta a function declaration instead of an object declaration: It would be a function that takes no arguments and returns a value of type Rectangle
这是因为空括号()使得recta是一个函数声明,而不是对象声明:Rectangle recta()是一个函数,没有任何参数的函数,返回值类型为Rectangle。
有点蒙?好了,这样记吧:
创建类对象是 类名 对象名。 因为需要传参,才有了括号。原本它是不需要括号的,因为没有参数传入。
是否还记得老师在上面说默认构造函数的时候,你趴在桌子上揉着惺忪是双眼,一脸迷茫的望着老师。
老师说:默认构造函数是没有参数的,如果你没有定义默认构造函数(无参构造函数),系统会自动给你生成一个无参的构造函数。但是,如果你定义了有参构造函数,那么系统不再为你生成无参构造函数。
啊?听懂了没?或者说,看到这里,你看懂了没?不要紧,你可以这样做:
①. 写上无参构造函数;
②. 写上有一个参数的构造函数;
③. 写上有两个参数的构造函数。
正如上面代码中写的那样:
Rectangle(); // 无参构造函数声明 Rectangle(int); // 一个参数构造函数声明 Rectangle(int ,int); // 两个参数构造函数声明
// 无参构造函数定义 Rectangle::Rectangle(){ width = 0; height = 0; } // 一个参数构造函数定义 Rectangle::Rectangle(int x){ width = x; height = 0; } // 两个参数构造函数定义 Rectangle::Rectangle(int x, int y){ width = x; height = y; }
在讲到函数重载的时候,还记得什么叫做函数重载吗?
在我的函数模板中写道这样一句话,当然,也是翻译的。。。我把它粘过来:
函数重载:具有不同的函数特性。 函数特性: 形参列表。 函数头部的返回类型,或void修饰符并不是这个函数的特性的一个部分。具有相同参数类型但不同返回类型的函数式不是函数重载。
不难发现,Rectangle类中的三个构造函数的参数个数都是不一样的,分别为0,1,2个。因此这些构造函数就是重载的。
再者,你是否觉得上面的代码是否可以缩减呢?构造函数那块,恩,可以的。使用默认参数列表就可以了。
看看这三个构造函数:
Rectangle(); Rectangle(int); Rectangle(int ,int);
是不是可以改写成:
Rectangle(int x=0 , int y=0); Rectangle(int x, int y=0); Rectangle(int,int);
当然,如果你在你的编译器中这样修改了,编译肯定是无法通过的。因为程序中如果有相同特性(identification)的函数,程序编译时无法通过的,需要删除其中一个,那么类中也是如此。
那么我们就删除前两个构造函数,只留下最后一个构造函数
Rectangle(int x , int y);在构造函数定义中
Rectangle::Rectangle(int x=0 , int y=0){ width = x; height = y; }看一下完整代码:
#include <iostream> using namespace std; class Rectangle { int width, height; public: Rectangle(int x , int y); // 构造函数声明 int area(void){ return (width*height); } }; // 构造函数定义 Rectangle::Rectangle(int x=0 , int y=0){ width = x; height = y; } int main() { Rectangle recta; Rectangle rectb(3); Rectangle rectc(3,4); cout<<"recta area: "<<recta.area()<<endl; cout<<"rectb area: "<<rectb.area()<<endl; cout<<"rectc area: "<<rectc.area()<<endl; return 0; }程序输出:
recta area: 0 rectb area: 0 rectc area: 12结果是一样的,不是吗?
紧接着我们将Rectangle构造函数的定义部分放到类中,就是这样:
class Rectangle { int width, height; // 类数据成员声明 public: Rectangle(int x =0 , int y =0){ width = x; height = y; } int area(void){ return (width*height); } };
再对构造函数 Rectangle(int x =0 , int y =0){ width = x; height = y; } 这一句做如下修改:
Rectangle(int x =0 , int y =0) :width(x), height(y) { }这是什么?初始化列表。让width的值等于x的值——widht(x)。height的值等于y的值——height(y)。
需要注意点什么?初始化列表的顺序,和类中数据成员(data member)的声明顺序要相同。类中数据成员的声明语句:int width, height;
如果顺序不一样会怎么样?编译器会报错,提示你的顺序不对:
In constructor 'Rectangle::Rectangle(int, int)':| warning: 'Rectangle::height' will be initialized after [-Wreorder]|这时候,调整一下顺序就好了。
下面是Rectangle类的构造函数重载,成员列表初始化的浓缩版本:
#include <iostream> using namespace std; class Rectangle { int width, height; public: Rectangle(int x =0 , int y =0) :width(x), height(y) { } int area(void){ return (width*height); } }; int main() { Rectangle recta; Rectangle rectb(3); Rectangle rectc(3,4); cout<<"recta area: "<<recta.area()<<endl; cout<<"rectb area: "<<rectb.area()<<endl; cout<<"rectc area: "<<rectc.area()<<endl; return 0; }程序输出:
recta area: 0 rectb area: 0 rectc area: 12