文章目录
三、构造函数
1. 什么是构造函数
建立一个对象时通常最需要立即做的就是初始化对象,如对数据成员赋初值,构造函数就是用来在创建对象时初始化对象,为对象赋初值的
- 类的数据成员不能在类定义时初始化的,因为类定义没有产生实体,而是给出了一个数据类型,不占用存储空间,因而无处容纳数据。
- 如果一个类的所有数据成员都是公有的,则可以在定义对象时对数据成员初始化,如果类中的数据成员是私有的则不能用这种方法初始化数据成员,因为在类的外部不能访问私有成员
- C++提供了构造函数来处理对象的初始化问题,构造函数是类的一种特殊成员函数,不需要人为调用,而是在建立对象时自动被执行
2. 构造函数的定义
定义构造函数
-
C++规定构造函数的名字与类的名字相同,并且不能指定返回类型,定义形式为:
类名(形参列表) { 函数体 }
或没有形参
类名() { 函数体 }
-
与其他任何函数一样,构造函数可以声明为内联的
-
只要创建类类型的新对象,都要执行构造函数,因此构造函数的主要用途就是初始化类的数据成员
-
构造函数定义初始化对象的一般形式
类名 对象名1(实参列表),对象名2(实参列表),...//有参数的构造函数初始化对象 或 类名 对象名1,对象名2,...//无参数的构造函数初始化对象
-
示例
#include<iostream> using namespace std; class Cuboid{ public: Cuboid(int l, int w, int h); int volumn() {return length * width * height;} private: int length, width, height; }; Cuboid::Cuboid(int l, int w, int h) //外部定义的构造函数 { length = l; width = w; height = h; cout << "Cuboid:" << "L:" << length << ' ' << "W:" << width << ' ' << "H:" << height << endl; } int main() { Cuboid a(1, 2, 3); //定义长方体对象a,使用构造函数初始化 cout << "volumn=" << a.volumn() << endl; return 0; }
说明:
- 构造函数是创建对象时自动执行的,而且只执行一次,并先于其他成员函数执行,构造函数不需要也不能人为调用
- 构造函数一般声明为公有的
- 在构造函数的函数体中不仅可以对数据成员初始化,而且可以包含任意其他功能的语句,但不提倡在构造函数中做与初始化无关的事
- 每个构造函数应该为每个数据成员提供初始化
- 带参数的构造函数中的形参,是在定义对象时由对应的实参给定的,实参必须与构造函数的形参的个数、次序、类型一致
构造函数初始化列表
-
与其他函数不同,构造函数可以包含一个形参初始化列表,形式如下:
类名(形参表):构造函数初始化列表 { 函数体 }
-
构造函数初始化列表只在构造函数定义中,而不是在构造函数声明中指定
-
从初始化角度看,构造函数分为两个阶段,且初始化先于普通计算阶段
- 初始化阶段(初始化列表)
- 普通计算阶段(函数体)
-
示例
Cuboid::Cuboid(int l, int w, int h):length(l), width(w), height(h) //外部定义的构造函数,,初始化列表 { cout << "Cuboid:" << "L:" << length << ' ' << "W:" << width << ' ' << "H:" << height << endl; }
说明:
有时必须用构造函数初始化列表:如果没有为类类型的数据成员提供初始化列表,则编译器会隐式的使用该成员的默认构造函数,若该成员没有默认构造函数,则报错。所以一般没有默认构造函数的成员,以及const或引用类型的成员都必须在构造函数初始化列表中初始化
class point { private: int x, y; public: point(int i, int j) { x=i, y=j;} //没有默认构造函数 void print(){ cout << x << y<< endl;} }; class pointTest { private: point a; public: pointTest(int i, int j):a(i,j){ } // 只能在初始化列表里对成员a初始化 }
成员初始化的次序
- 每个成员的在构造函数初始化列表中只能指定一次
- 构造函数初始化列表仅指定用于初始化的数据成员的值,并不指定这些初始化执行的次序,数据成员被初始化的次序就是数据成员的声明次序
初始化式可以是任意表达式
3. 构造函数的重载
-
在一个类中允许定义多个构造函数版本,即允许构造函数重载
-
尽管一个类中可以定义多个构造函数,但对于每一个对象来说,建立对象时只执行其中一个
-
示例
#include<iostream> using namespace std; class Point{ public: Point() {x = y =0;} // 无参数的构造函数 Point(int a, int b):x(a),y(b){} // 有参数的构造函数 void display(){ cout << x << y << endl;} private: int x, y; }; int main() { Point m; //无参数构造函数初始化 m.display(); Point n(1, 2); //有参数构造函数初始化 n.display(); return 0; }
4. 带默认参数的构造函数
-
构造函数的参数允许使用默认值
-
示例
#include<iostream> using namespace std; class Point{ public: Point(int a=1, int b=2):x(a),y(b){} // 有参数的构造函数 void display(){ cout << x << y << endl;} private: int x, y; }; int main() { Point m, n(0), k(0, 0); //无参数构造函数初始化 m.display(); n.display(); k.display(); return 0; }
说明:
- 必须在类的内部指定默认参数
- 如果构造函数的全部参数都指定了默认值,则在定义对象时可以给出任意个实参
- 一般不同时使用构造函数的重载和带默认参数的构造函数
5. 默认构造函数
定义默认构造函数
-
默认构造函数就是在没有显式提供初始化式时调用的构造函数,它是一个不带参数的构造函数
-
定义默认构造函数的一般形式
类名() { 函数体 }
-
与默认构造函数相对应的对象定义形式为
类名 对象名;
说明:
- 任何一个类有且只有一个默认构造函数。如果定义的类中没有显式定义任何构造函数,编译器会自动为该类生成默认构造函数,称为合成默认构造函数
- 一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数
- 一般任何一个类都应定义一个默认构造函数
隐式类类型转换
-
为了实现其他类型到类类型的隐式装换,需要定义合适的构造函数。可以用单个实参调用的构造函数(称为转换构造函数)定义从形参到该类类型的隐式转换
#include<iostream> using namespace std; class Data{ public: Data(const string& str=""):s1(str){ } void SetString(const Data& r) { s1 = r.s1; } // 期待的是Data类型的对象 void print() { cout << s1 << endl; } private: string s1; }; int main() { Data a, b, c("world"); string i = "string"; a.SetString(c); // c是Data类的一个对象 b.SetString(string("world")); // 隐式转换 a.print(); b.print(); Data d = Data(i); // 隐式转换,i是string类的一个对象 d.print(); return 0; }
-
使用单个参数的构造函数来进行类类型转换的方法
- 先声明一个类
- 在这个类中定义一个只有一个参数的构造函数,参数的类型需要转换的数据类型
- 采用转换构造函数定义对象是即进行类型转换
说明:
- 可以禁止由构造函数定义的隐式转换,方法是通过将构造函数声明为explicit
- explicit关键字只能用于类内部的构造函数声明上,在类定义外部不能重复它
- 一般地,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit
6. 复制构造函数与合成复制构造函数
复制构造函数
-
复制构造函数又称为拷贝构造函数,他的作用是用一个已经生成的对象来初始化另一个同类的对象,定义的一般形式为:
类名(const 类名& obj) { 函数体 }
-
示例
class Point{ public: Point() : x(0), y(0) { } // 默认构造函数 Point(const Point& r) : x(r.x), y(r.y) { } // 复制构造函数 Point(int a, int b) : x(a), y(b) { } // 带参数的构造函数 private: int x, y; };
-
与复制构造函数对应的对象的定义形式为
类名 对象名1(类对象1),对象名2(类对象2),...;
说明:
- 复制构造函数有且只有一个本类型对象的引用形参,通常使用const限定,因为复制构造函数只是复制对象,没有必要改变传递来的对象的值
- 复制构造函数的功能是利用一个已知的对象来初始化一个被创建的同类的对象
- 对象赋值和对象复制的区别:
- 对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值
- 对象的复制是从无到有地建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)
合成复制构造函数
-
每个类必须有一个复制构造函数,如果类没有定义复制构造函数,编译器会自动合成一个,称为合成复制构造函数
-
与合成默认构造函数不同,即使定义了其他构造函数,编译器也会合成复制构造函数
-
合成构造函数的操作是:执行逐个成员初始化,将新对象初始化为原对象的副本
- 复制逐个成员是指编译器将现对象的每个非静态数据成员一次复制到正创建的对象中,每个成员的类型决定了复制该成员的含义
- 内置类型成员直接复制其值
- 类类型成员使用该类的复制构造函数进行复制
- 如果一个类具有数组成员,则合成复制构造函数将复制数组,即复制数组中每一个元素到新对象中
- 复制逐个成员是指编译器将现对象的每个非静态数据成员一次复制到正创建的对象中,每个成员的类型决定了复制该成员的含义
-
以下3种情况使用复制构造函数
-
用一个对象显式或隐式初始化另一个对象
- 复制初始化,使用等号(=),调用复制构造函数
- 直接初始化,调用与实参匹配的构造函数
Point pt1(10, 20); Point pt2 = pt1; // 复制初始化 Point pt3(pt1); // 直接初始化
-
函数参数按值传递对象时,或函数返回对象时
-
根据元素初始化式列表初始化数组元素时
-
7. 深复制和浅复制
- 深复制:如果一个拥有资源的类对象发生复制时,若对象数据与资源内容一起复制,称为深复制
-
若复制对象但未复制资源内容称为浅复制
-
示例
#include<iostream> #include<string.h> using namespace std; class CA{ public: CA(int b, char *cstr) // { a=b; str = new char[b]; strcpy(str, cstr); } CA(const CA &C) { a = C.a; str = new char[a]; if(str != 0) strcpy(str, C.str); } void show() { cout << str << endl; } ~CA() // { delete str; } private: int a; char *str; }; int main() { CA a(10,"hell0"); CA b = a; b.show(); return 0; }