创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。注意,类的数据成员是不能在声明类时初始化的。
为了解决这个问题,C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
1构造和析构函数
1构造函数和析构函数的概念
有关构造函数
1构造函数定义及调用
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
2)构造函数在定义时可以有参数;
3)没有任何返回类型的声明。
2构造函数的调用
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数
有关析构函数
3析构函数定义及调用
1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:~ClassName()
2)析构函数没有参数也没有任何返回类型的声明
3)析构函数在对象销毁时自动被调用
4)析构函数调用机制
#include <iostream>
#include<string.h>
using namespace std;
class Test
{
public:
Test() //无参数 构造函数
{
a = 10; //作用完成对属性的初始化工作
p = new char[100];
strcpy(p, "aaaaffff");
cout<<"我是构造函数 被执行了"<<endl;
}
void print()
{
cout<<p<<endl;
cout<<a<<endl;
}
~Test() //析构函数
{
if (p != NULL)
{
cout<<"p不为空所以要清除"<<endl;
delete []p;
}
cout<<"我是析构函数,被调用了" <<endl;
}
protected:
private:
int a ;
char *p;
};
//给对象搭建一个舞台,研究对象的行为
void objplay()
{
//先创建的对象 后释放
Test t1;
t1.print();
printf("分隔符\n");
Test t2;
t2.print();
}
int main()
{
objplay();
//t1和t2的作用域消失,所以会自动调用析构函数
cout<<"hello..."<<endl;
return 0;
}
2 C++编译器构造析构方案 PK 对象显示初始化方案
设计构造函数和析构函数的原因
面向对象的思想是从生活中来,手机、车出厂时,是一样的。
生活中存在的对象都是被初始化后才上市的;初始状态是对象普遍存在的一个状态的
普通方案:
为每个类都提供一个public的initialize函数;
对象创建后立即调用initialize函数进行初始化。
优缺点分析
1)initialize只是一个普通的函数,必须显示的调用
2)一旦由于失误的原因,对象没有初始化,那么结果将是不确定的
没有初始化的对象,其内部成员变量的值是不定的
3)不能完全解决问题
//为什么对象需要初始化 有什么样的初始化方案
#include "iostream"
using namespace std;
/*
思考为什么需要初始化
面向对象思想来自生活,手机、车、电子产品,出厂时有初始化
怎么样进行初始化?
方案1:显示调用方法
缺点:易忘、麻烦;显示调用init,不能完全解决问题
*/
/*
这是类的常成员函数,声明格式 : 类型说明符 函数名(参数表) const
常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数(这就保证常成员函数绝对不会更改数据成员值)
如果将一个对象声明为常对象,则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数
*/
class Test21
{
public:
int getM() const { return m; }
void setM(int val) { m = val; }
int getN() const { return n; }
void setN(int val) { n = val; }
public:
int init(int m,int n)
{
this->m = m;
this->n = n;
return 0;
}
protected:
int x;
private:
int m;
int n;
};
int main()
{
Test21 t1; //无参构造函数的调用方法
Test21 t2;
//t1.init(100, 200);
//t2.init(300, 400);
cout<<t1.getM()<<" "<<t1.getN()<<endl;
cout<<t2.getM()<<" "<<t2.getN()<<endl;
//定义对象数组时,没有机会进行显示初始化
Test21 arr[3];
//Test21 arr_2[3] = {Test21(1,3), Test21(), Test21()};
return 0;
}
2构造函数的分类及调用
C++编译器给程序员提供的对象初始化方案,高端大气上档次。
//有参数构造函数的三种调用方法 class Test { private: int a; int b;
public:
//无参数构造函数 Test() { ; }
//带参数的构造函数 Test(int a, int b) { ; } //赋值构造函数 Test(const Test &obj) { ; }
public: void init(int _a, int _b) { a = _a; b = _b; } }; |
1无参数构造函数
调用方法: Test t1, t2;
2有参构造函数
有参构造函数的三种调用方法
//有参数构造函数的三种调用方法
#include<iostream>
class Test5
{
private:
int a;
public:
//带参数的构造函数
Test5(int a)
{
printf("\na:%d", a);
}
Test5(int a, int b)
{
printf("\na:%d b:%d", a, b);
}
public:
};
int main()
{
Test5 t1(10); //c++编译器默认调用有参构造函数 括号法
Test5 t2 = (20, 10); //c++编译器默认调用有参构造函数 等号法
Test5 t3 = Test5(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法
return 0;
}
3拷贝构造函数调用时机
赋值构造函数的四种调用场景(调用时机)
第1和第2个调用场景
#include<iostream>
#include "iostream"
using namespace std;
class AA
{
public:
AA() //无参构造函数 默认构造函数
{
cout<<"我是构造函数,自动被调用了"<<endl;
}
AA(int _a) //无参构造函数 默认构造函数
{
a = _a;
}
AA(const AA &obj2)
{
cout<<"我也是构造函数,我是通过另外一个对象obj2,来初始化我自己"<<endl;
a = obj2.a + 10;
}
~AA()
{
cout<<"我是析构函数,自动被调用了"<<endl;
}
void getA()
{
printf("a:%d \n", a);
}
protected:
private:
int a;
};
//单独搭建一个舞台
void ObjPlay01()
{
AA a1; //变量定义
//赋值构造函数的第一个应用场景
//用对象1 初始化 对象2
AA a2 = a1; //定义变量并初始化 //初始化法
a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
}
//第二个应用场景
//单独搭建一个舞台
void ObjPlay02()
{
AA a1(10); //变量定义
//赋值构造函数的第一个应用场景
//用对象1 初始化 对象2
AA a2(a1); //定义变量并初始化 //括号法
//a2 = a1; //用a1来=号给a2 编译器给我们提供的浅copy
a2.getA();
}
//注意:初始化操作 和 等号操作 是两个不同的概念
int main()
{
ObjPlay01();
cout<<"==============="<<endl;
ObjPlay02();
return 0;
}
#include <iostream>
using namespace std;
class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
//copy构造函数 完成对象的初始化
Location(const Location & obj) //copy构造函数
{
X = obj.X; Y = obj.Y;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;
//业务函数 形参是一个元素
void f(Location p)
{
cout<<p.GetX()<<endl;
}
void playobj()
{
Location a(1, 2);
Location b = a;
cout<<"b对象已经初始化完毕"<<endl;
f(b); //b实参取初始化形参p,会调用copy构造函数
}
int main()
{
playobj();
cout<<"hello..."<<endl;
return 0;
}
#include <iostream>
using namespace std;
class Location{
public:
Location(int xx = 0, int yy = 0){
X = xx; Y = yy; cout << "Constructor Object.\n";
}
Location(const Location& obj){//copy构造函数
X = obj.X;
Y = obj.Y;
}
~Location(){
cout << X << "," << Y <<" "<< "Object destroyed." << endl;
}
int GetX(){ return X; }
int GetY(){ return Y; }
private:
int X;
int Y;
};
//g()返回一个元素
//结论1:函数的返回值是一个元素(复杂类型),返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数)
//
//结论2:匿名对象的去和留
//如果用匿名对象初始化 另外一个同类型的对象,匿名对象转成有名对象
//如果用匿名对象赋值给 另外一个同类型的对象,匿名对象被析构
Location g(){
Location A(-1, -2);
return A;//A是一个局部变量,当生命周期完毕,会调用析构函数释放空间。此时新建了一个匿名对象来接返回值,因此会调用匿名对象类的复制构造函数,相当于A来初始化匿名对象
}
void playobj01(){
cout << "playobj01执行开始\n" << endl;
g();//匿名对象会被析构掉;
printf("匿名对象会析构掉\n");
cout << "playobj01执行完毕\n" << endl;
}
void playobj02(){
cout << "playobj02执行开始\n" << endl;
//用匿名对象初始化m 此时c++编译器 直接把匿名对象转成了m
Location m = g();
printf("匿名对象被转正,不会析构掉\n");//速度就会加快
cout << m.GetX() << endl;
cout << "playobj02执行完毕\n" << endl;
}
void playobj03(){
cout << "playobj03执行开始\n" << endl;
Location m2(1, 2);
m2 = g();
printf("因此用匿名对象赋值给m2,匿名对象被析构掉\n");
cout << m2.GetX() << endl;
cout << "playobj03执行完毕\n" << endl;
}
int main()
{
playobj01();
playobj02();
playobj03();
system("pause");
return 0;
}
4默认构造函数
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
3.3构造函数调用规则研究
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数
#include <iostream>
using namespace std;
class Test
{
public:
// Test(const Test& obj) //copy构造函数 作用: 用一个对象初始化另外一个对象
// {
// a = obj.a + 100;
// b = obj.b + 100;
// }
// Test(int _a, int _b)
// {
// ;
// }
Test()
{
}
void printT()
{
cout << "a:" << a << "b: "<<b<< endl;
}
protected:
private:
int a;
int b;
};
//当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
//当类中定义了有参数构造函数是,c++编译器不会提供无参数构造函数
//在定义类时, 只要你写了构造函数,则必须要用
void main81()
{
//Test t1; //调用无参构造函数
cout<<"hello..."<<endl;
system("pause");
return ;
}
注意:编译器提供的拷贝构造都是浅拷贝,那么浅拷贝和深拷贝的区别是什么呢?
对象2是对象1的浅复制,对象3是对象1的深复制,他们的区别在于浅复制只会copy对象中的变量,而不会复制成员指针变量所指向的内存空间。编译器提供的都是浅拷贝,深拷贝需要人为去定义。
还需要注意一项就是编译器提供的析构函数不会释放成员指针所指向的内存,需要人为去释放
构造函数有个特殊的问题:
class Test {
public:
Test() {
}
Test(int _a, int _b) {
a = _a;
b = _b;
Test(1, 3, 4);//这个时候c会不会被赋值呢????
}
Test(int _a, int _b, int _c) {
a = _a;
b = _b;
c = _c;
}
protected:
private:
int a;
int b;
int c;
};
int main() {
Test t(1, 2);
}
对象t中的c会不会被赋值呢?不会。构造函数中Test(1,3,4)只会生成一个匿名对象然后消失。并不会给对象t的c成员变量赋值
3.5多个对象构造和析构
1对象初始化列表
1)对象初始化列表出现原因
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
如果没有初始化列表,那么他将无法完成第一步,就会报错。
2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
2)C++中提供初始化列表对成员变量进行初始化
语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
3)注意概念
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
4)注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行
#include <iostream>
using namespace std;
class A
{
public:
A(int _a)
{
a = _a;
cout << "构造函数" << "a" << a << endl;
}
~A()
{
cout << "析构函数" << "a" << a << endl;
}
protected:
private:
int a;
};
//1 构造函数的初始化列表 解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数)
//根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A
//新的语法 Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
class B
{
public:
B(int _b1, int _b2) : a1(1), a2(2), c(0)
{
}
B(int _b1, int _b2, int m, int n) : a1(m), a2(n), c(0)
{
b1 = _b1;
b2 = _b2;
cout <<"B的构造函数"<<endl;
}
~B()
{
cout<<"B的析构函数" <<endl;
}
protected:
private:
int b1;
int b2;
A a2;
A a1;
const int c;
};
//2 先执行 被组合对象的构造函数
//如果组合对象有多个,按照定义顺序, 而不是按照初始化列表的顺序
//析构函数 : 和构造函数的调用顺序相反
//3 被组合对象的构造顺序 与定义顺序有关系 ,与初始化列表的顺序没有关系.
//4 初始化列表 用来 给const 属性赋值
void obj10play()
{
//A a1(10);
//B ojbB(1, 2);
//1参数传递
B ojbB2(1, 2,3, 4);
//2 调用顺序
return ;
}
int main()
{
obj10play();
return 0;
}
类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
//对象做函数参数
//1 研究拷贝构造
//2 研究构造函数,析构函数的调用顺序
//总结 构造和析构的调用顺序
#include "iostream"
using namespace std;
class ABCD
{
public:
ABCD(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
printf("ABCD() construct, a:%d,b:%d,c:%d \n", this->a, this->b, this->c);
}
~ABCD()
{
printf("~ABCD() construct,a:%d,b:%d,c:%d \n", this->a, this->b, this->c);
}
int getA()
{
return this->a;
}
protected:
private:
int a;
int b;
int c;
};
class MyE
{
public:
MyE():abcd1(1,2,3),abcd2(4,5,6)
{
cout<<"MyD()"<<endl;
}
~MyE()
{
cout<<"~MyD()"<<endl;
}
MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100)
{
printf("MyD(const MyD & obj)\n");
}
protected:
//private:
public:
ABCD abcd1; //c++编译器不知道如何构造abc1
ABCD abcd2;
const int m=5;
};
int doThing(MyE mye1)
{
printf("doThing() mye1.abc1.a:%d \n", mye1.abcd1.getA());
return 0;
}
int run2()
{
MyE myE;
doThing(myE);
return 0;
}
//
int run3()
{
printf("run3 start..\n");
//ABCD(400, 500, 600); //临时对象的生命周期
ABCD abcd = ABCD(100, 200, 300);
//若直接调用构造函数呢?
//想调用构造函数对abc对象进行再复制,可以吗?
//在构造函数里面调用另外一个构造函数,会有什么结果?
printf("run3 end\n");
return 0;
}
int main()
{
//run2();
run3();
return 0;
}