四.类和对象
2.对象的初始化和清理
- 电脑、手机等电子产品基本都有“出厂设置”,我们可以删除一些隐私数据以保安全
- C++的面向对象来自生活,每个对象也都有初始设置和对象销毁的清理数据选项
一.构造函数和析构函数
1.概念
- 构造函数:创建对象时为对象的成员属性赋值(也就是初始化程序),编译器自动调用
- 析构函数:对象撤销前系统自动调用,执行一些清理工作
- 构造函数和析构函数是完成对象初始化和清理工作的(编译器会自动调用),如果编程中不提供这两个函数的话,编译器会自动提供,但是编译器提供的是空的。
2.构造函数的语法:类名(){}
- 构造函数没有返回值也不用写void
- 函数名与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
-
#include
using namespace std;
class person
{
public:
//构造函数 类名(){}
person()//无须加viod、可以有参数也可以无参数,函数名和类名是相同的(都是person)
{
cout << "构造函数的调用" << endl;//你不写这个的话,编译器也会给你写,
//只不过,编译器写的是空的,也就是一个黑屏,什么也没有的
//而你写了,屏幕上就显示你写的东西
}
};
void test01()
{
person p;
}
int main()
{
test01();//屏幕上值出现一次,说明了值调用一次构造函数
system("pause");
return 0;
}
3.析构函数语法:类名(){}
- 构造函数没有返回值也不用写void
- 函数名与类名相同,在名称前加上符号 ~
- 构造函数可以没有参数,因此不可以发生重载
- 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
-
#include
using namespace std;
//构造函数
class person
{
public:
person()//无须加viod、可以有参数也可以无参数
{
cout << "构造函数的调用" << endl;//你不写这个的话,编译器也会给你写,
//只不过,编译器写的是空的,也就是一个黑屏,什么也没有的
//而你写了,屏幕上就显示你写的东西
}
//析构函数:~类名(){}
~person()//无须加viod、不可以有参数
{
cout << "析构函数的调用" << endl;
}
};
void test01()
{
person p;//在栈上,test01执行完成后,释放这个对象
}
int main()
{
test01();//屏幕上值出现一次,说明了值调用一次构造函数
system("pause");
return 0;
}
二.构造函数的分类及调用
1.两种分类
- 按参数分类:有参数和无参数(默认构造)
- 按类型分类:普通构造和拷贝构造(也就是复制的意思)
2.三种调用函数
- 括号发
- 显示法
- 隐式转换法
3.注意:
-
调用默认构造函数的时候,不要加括号(因为编译器会认为这样写是函数的声明)
-
不要利用拷贝函数来初始化匿名对象
-
#include
using namespace std;
class person
{
public:
//构造函数
person()
{
cout << "构造无参函数的调用" << endl;
}
person(int a)
{
age = a;
cout << "构造有参函数的调用" << endl;
}
//拷贝构造函数
person(const person &p)//要用引用常量的方式拷贝
{
age = p.age;//将传入的人的所有的属性拷贝到当前对象的身上
cout << "拷贝构造函数的调用" << endl;
}
~person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
//调用
void test01()
{
//括号法
cout << "括号法调用:" << endl;
person p1;//默认函数构造调用
person p2(10);//有参函数的调用
person p3(p2);//拷贝函数的调用
cout << "p2的年龄:" << p2.age << endl;//上边传入了10,所以p2的年龄是10
cout << "p3的年龄:" << p3.age << endl;//将p2的年龄拷贝入了p3,所以p3也是10
//显示法
cout << "显示法调用:" << endl;
person p4;//默认构造
person p5 = person(10);//有参函数调用
person p6 = person(p5);//拷贝函数调用
person(10);//匿名对象 特点:当前行执行完之后,系统会立即回收掉匿名对象
cout << "aaa" << endl;//匿名对象先被释放后再来执行这个输出
//拷贝函数初始化匿名对象
//person(p5);//编译器认为person(p5)=person p5,编译器会报错
cout << "隐式转换法" << endl;
//隐式转换法
person p7 = 10;//相当于person p7 =person (10) 有参构造
person p8 = p7;//拷贝构造
}
int main()
{
test01();
system("pause");
return 0;
}
三.拷贝函数的调用时机
用到的情况
- 使用一个已经创建完毕的对象来初始化一个新的对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include
using namespace std;
class person
{
public:
person()
{
cout << "默认构造函数的调用" << endl;
}
person(int age)//初始化age
{
cout << "有参构造函数的调用" << endl;
m_age = age;
}
person(const person &p)//拷贝函数
{
cout << "拷贝函数的调用" << endl;
m_age = p.m_age;
}
~person()
{
cout << "析构函数的调用" << endl;
}
int m_age;
};
//使用一个已经创建完毕的对象来初始化一个对象
void test01()
{
person p1(20);
person p2(p1);
}
//值传递的形式给函数传值
void dowork(person p)
{
}
void test02()
{
person p;
dowork(p);
}
//值方式返回局部对象
person dowork03()
{
person p3;//调用默认函数
cout << (int*)&p3 << endl;
return p3;//调用拷贝函数
//根据p3拷贝一个新的对象,返回
}
void test03()
{
person p = dowork03();
cout << (int*)&p << endl;
}
int main()
{
cout << "01" << endl;
test01();
cout << "02" << endl;
test02();
cout << "03" << endl;
test03();
system("pause");
return 0;
}
四.构造函数调用规则
1.默认情况 ,c++编程至少给一个类添加三个函数
- 电脑、手机等电子产品基本都有“出厂设置”,我们可以删除一些隐私数据以保安全
- C++的面向对象来自生活,每个对象也都有初始设置和对象销毁的清理数据选项
- 构造函数:创建对象时为对象的成员属性赋值(也就是初始化程序),编译器自动调用
- 析构函数:对象撤销前系统自动调用,执行一些清理工作
- 构造函数和析构函数是完成对象初始化和清理工作的(编译器会自动调用),如果编程中不提供这两个函数的话,编译器会自动提供,但是编译器提供的是空的。
- 构造函数没有返回值也不用写void
- 函数名与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
-
#include
using namespace std; class person { public: //构造函数 类名(){} person()//无须加viod、可以有参数也可以无参数,函数名和类名是相同的(都是person) { cout << "构造函数的调用" << endl;//你不写这个的话,编译器也会给你写, //只不过,编译器写的是空的,也就是一个黑屏,什么也没有的 //而你写了,屏幕上就显示你写的东西 } }; void test01() { person p; } int main() { test01();//屏幕上值出现一次,说明了值调用一次构造函数 system("pause"); return 0; }
- 构造函数没有返回值也不用写void
- 函数名与类名相同,在名称前加上符号 ~
- 构造函数可以没有参数,因此不可以发生重载
- 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
-
#include
using namespace std; //构造函数 class person { public: person()//无须加viod、可以有参数也可以无参数 { cout << "构造函数的调用" << endl;//你不写这个的话,编译器也会给你写, //只不过,编译器写的是空的,也就是一个黑屏,什么也没有的 //而你写了,屏幕上就显示你写的东西 } //析构函数:~类名(){} ~person()//无须加viod、不可以有参数 { cout << "析构函数的调用" << endl; } }; void test01() { person p;//在栈上,test01执行完成后,释放这个对象 } int main() { test01();//屏幕上值出现一次,说明了值调用一次构造函数 system("pause"); return 0; }
- 按参数分类:有参数和无参数(默认构造)
- 按类型分类:普通构造和拷贝构造(也就是复制的意思)
- 括号发
- 显示法
- 隐式转换法
-
调用默认构造函数的时候,不要加括号(因为编译器会认为这样写是函数的声明)
-
不要利用拷贝函数来初始化匿名对象
-
#include
using namespace std; class person { public: //构造函数 person() { cout << "构造无参函数的调用" << endl; } person(int a) { age = a; cout << "构造有参函数的调用" << endl; } //拷贝构造函数 person(const person &p)//要用引用常量的方式拷贝 { age = p.age;//将传入的人的所有的属性拷贝到当前对象的身上 cout << "拷贝构造函数的调用" << endl; } ~person() { cout << "析构函数的调用" << endl; } int age; }; //调用 void test01() { //括号法 cout << "括号法调用:" << endl; person p1;//默认函数构造调用 person p2(10);//有参函数的调用 person p3(p2);//拷贝函数的调用 cout << "p2的年龄:" << p2.age << endl;//上边传入了10,所以p2的年龄是10 cout << "p3的年龄:" << p3.age << endl;//将p2的年龄拷贝入了p3,所以p3也是10 //显示法 cout << "显示法调用:" << endl; person p4;//默认构造 person p5 = person(10);//有参函数调用 person p6 = person(p5);//拷贝函数调用 person(10);//匿名对象 特点:当前行执行完之后,系统会立即回收掉匿名对象 cout << "aaa" << endl;//匿名对象先被释放后再来执行这个输出 //拷贝函数初始化匿名对象 //person(p5);//编译器认为person(p5)=person p5,编译器会报错 cout << "隐式转换法" << endl; //隐式转换法 person p7 = 10;//相当于person p7 =person (10) 有参构造 person p8 = p7;//拷贝构造 } int main() { test01(); system("pause"); return 0; }
- 使用一个已经创建完毕的对象来初始化一个新的对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#includeusing namespace std; class person { public: person() { cout << "默认构造函数的调用" << endl; } person(int age)//初始化age { cout << "有参构造函数的调用" << endl; m_age = age; } person(const person &p)//拷贝函数 { cout << "拷贝函数的调用" << endl; m_age = p.m_age; } ~person() { cout << "析构函数的调用" << endl; } int m_age; }; //使用一个已经创建完毕的对象来初始化一个对象 void test01() { person p1(20); person p2(p1); } //值传递的形式给函数传值 void dowork(person p) { } void test02() { person p; dowork(p); } //值方式返回局部对象 person dowork03() { person p3;//调用默认函数 cout << (int*)&p3 << endl; return p3;//调用拷贝函数 //根据p3拷贝一个新的对象,返回 } void test03() { person p = dowork03(); cout << (int*)&p << endl; } int main() { cout << "01" << endl; test01(); cout << "02" << endl; test02(); cout << "03" << endl; test03(); system("pause"); return 0; }
- 默认构造函数(无参 函数体为空)
- 默认析构函数(无参 函数体为空)
- 默认拷贝函数,对对象属性进行拷贝
- 如果用户定义有参构造函数,c++不在默认无参构造函数,但是提高无参析构函数
- 如果用户定义拷贝构造函数,c++不在提供任何其他构造函数
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
- 总结:如果属性有在堆区开辟的,一定要自己提供拷贝函数,防止浅拷贝带来的问题
1.浅拷贝问题:堆区重复释放
#includeusing namespace std; class person { public: person() { cout << "默认构造函数的调用" << endl; } person(int age,int height)//初始化age { cout << "有参构造函数的调用" << endl; m_age = age; m_height=new int(height);//堆区开辟的对象有程序员开辟也有程序员释放 } ~person() { //析构代码:将堆区开辟的数据做释放操作 if (m_height != NULL) { delete m_height; m_height = NULL; } cout << "析构函数的调用" << endl; } int m_age; int* m_height;//身高 }; //使用一个已经创建完毕的对象来初始化一个对象 void test01() { person p1(20,163); cout << "p1的年龄为:" << p1.m_age<<"p1的身高为:" <<*p1.m_height<< endl; person p2(p1); cout << "p2的年龄为:" << p2.m_age << "p2的身高为:" << *p2.m_height << endl; } int main() { cout << "01" << endl; test01(); system("pause"); return 0; }
2.浅拷贝问题解决:利用深拷贝解决
- 自己实现拷贝构造函数,解决浅拷贝带来的问题
- 深拷贝
- 重新开辟一个堆区,也就是原先的p1指向的堆区是一个,而现在的p2指向的堆区是另一个
person(const person &p)//拷贝函数
{
cout << "拷贝函数的调用" << endl;
m_age = p.m_age;
//m_height = height;//编译器默认的拷贝函数的代码
//深拷贝操作
m_height = new int(*p.m_height);
}
六.初始化列表
- 作用:用来初始化属性
- 语法:构造函数():属性1(值1),属性2(值2)....{}
#include七.类对象最为类成员using namespace std; class person { public: //传统初始化操作 //初始化列表初始属性 person(int a,int b,int c) :m_a(a), m_b(b), m_c(c) { } int m_a; int m_b; int m_c; }; void test01() { //person p(10, 20, 30); person p(10,20,30); cout << "m_a:" << p.m_a << endl; cout << "m_b:" << p.m_b << endl; cout << "m_b:" << p.m_c << endl; } int main() { test01(); system("pause"); return 0; }
- 对象成员:类中的成员是另一个类的对象
-
class A { } class B { A a;//B类中对象A作为成员,A叫做对象成员 } - 当其他类的对象作为本类的成员,先构造其他类,再构造自身;析构的顺序与构造相反
-
#include
using namespace std; #include class phone { public: phone(string pname) { cout << "phone构造函数调用" << endl; p_name = pname; } ~phone() { cout << "phone析构函数调用" << endl; } string p_name; }; class person { public: //phone p_name=pname 隐式转换法 person(string name, string pname) :m_name(name), m_phone(pname) { cout << "person构造函数调用" << endl; } ~person() { cout << "person析构函数调用" << endl; } string m_name; phone m_phone; }; //当其他类的对象作为本类的成员,先构造其他类,再构造自身。 //析构的顺序与构造相反 void test01() { person p("张三", "苹果MAX"); cout << p.m_name << "拿着:" << p.m_phone.p_name << endl; } int main() { test01(); system("pause"); return 0; }
静态成员就是在成员变量或成员函数前加“static”
1.静态成员变量
- 所有共享同一份数据
- 在编译阶段分配
- 类内申明,类外初始化
2.静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量



