目录
1、构造函数和析构函数
2、构造函数的分类及调用
3、拷贝构造函数的调用时机,什么时候会用到拷贝构造函数
4、构造函数的调用规则
4.1只要写了一个类,C++编译器都会给每个类至少添加三个函数
4.2如果我们写了有参构造函数,编译器就不再提供默认构造函数,依然提供拷贝构造函数
4.3 如果我们写了拷贝构造函数,那么编译器就不再提供其他的构造函数
5、深拷贝和浅拷贝
5.1浅拷贝
编辑5.2深拷贝
6、初始化列表--给类中的属性进行初始化操作
7、类对象作为类成员(类中的成员可以是另一个类的对象)--构造和析构的顺序
8、静态成员
8.1静态成员变量
8.1.1共享同一份内存,类外初始化
8.1.2静态成员变量有两种访问方式--通过对象进行访问,通过类名进行访问
8.2静态成员函数--只能访问静态成员变量,访问方式有2种
1、构造函数和析构函数
#include<iostream>
using namespace std;
//对象的初始化和清理
class Person
{
public:
//1、1构造函数 来进行初始化的操作
//没有返回值,也不用写void
//函数名域类名相同
//构造函数可以有参数,可以发生重载
//创建对象的时候,构造函数会自动调用,而且只调用一次
//不写构造函数,编译器提供的是空实现,函数内部什么语句也没有
/*这样的
Person()
{
}
*/
Person()
{
cout << "Person 构造函数的调用" << endl;
}
//2、析构函数 来进行清理的操作
//没有返回值,也不写void
//函数名和类名相同,在名称加~
//析构函数不可以有参数,不可以发生重载
//对象在销毁前会自动调用析构函数,而且只会调用一次
//不写析构函数,编译器提供的是空实现,函数内部什么语句也没有
/*这样的
~Person()
{
}
*/
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
//构造和析构都是必须有的实现,如果我们自己不提供,
//编译器会提供一个空实现(里面一行代码都么有)的构造和析构
void test01()
{
Person p;//局部变量,栈区数据,test01执行完毕后,释放就会这个对象
}
int main()
{
test01();
/*输出
Person 构造函数的调用
Person 析构函数的调用
*/
system("pause");//按任意键继续
return 0;
}
#include<iostream>
using namespace std;
//对象的初始化和清理
class Person
{
public:
//1、1构造函数 来进行初始化的操作
//没有返回值,也不用写void
//函数名域类名相同
//构造函数可以有参数,可以发生重载
//创建对象的时候,构造函数会自动调用,而且只调用一次
//不写构造函数,编译器提供的是空实现,函数内部什么语句也没有
/*这样的
Person()
{
}
*/
Person()
{
cout << "Person 构造函数的调用" << endl;
}
//2、析构函数 来进行清理的操作
//没有返回值,也不写void
//函数名和类名相同,在名称加~
//析构函数不可以有参数,不可以发生重载
//对象在销毁前会自动调用析构函数,而且只会调用一次
//不写析构函数,编译器提供的是空实现,函数内部什么语句也没有
/*这样的
~Person()
{
}
*/
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
//构造和析构都是必须有的实现,如果我们自己不提供,
//编译器会提供一个空实现(里面一行代码都么有)的构造和析构
void test01()
{
Person p;//局部变量,栈区数据,函数(test01)执行完毕后,释放就会这个对象
}
int main()
{
Person p;
/*
输出:Person 构造函数的调用
*/
//欸?怎么没有析构,因为我们函数还没执行完
//还有下面的按任意键继续
//盯着黑窗口看,会看到析构函数调用一闪而过
system("pause");//按任意键继续
return 0;
}
2、构造函数的分类及调用
#include<iostream>
using namespace std;
//构造函数的分类及调用
//分类
//按照参数分类 无参构造(也称默认构造)和有参构造
//按照类型分类 普通构造函数 拷贝构造函数,不是拷贝构造的,都是普通构造
class Person
{
public:
Person()
{
cout << "Person的无参构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person的有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person &p)//形参和当前类名一样,const防止对它进行修改
{//作用:比如传进来个张三,复制出来一个和它一模一样的
//将传入的人身上所有的属性,拷贝到当前对象身上
//谁调用这个拷贝构造就传给谁
age = p.age;
cout << "Person的拷贝构造函数" << endl;
}
~Person()
{
cout << "Person的构析构函数的调用" << endl;
}
int getAge()
{
return age;
}
private:
int age;
};
//构造函数的调用
void test01()
{
//1、括号法,推荐使用
Person p1;//默认构造函数(无参构造函数)的调用
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
cout << "p2的年龄:" << p2.getAge() << endl;
cout << "p3的年龄:" << p3.getAge() << endl;
//注意事项1:
//调用默认构造函数的时候,不要加()
// 因为下面这行代码编译器会认为是一个函数的声明,不会认为在创建对象
// Person p4()
void func();//在一个函数体内是可以写另一个函数的声明的
//2、显示法
cout << "显示法" << endl;
Person p5;//默认构造函数(无参构造函数)的调用
Person p6 = Person(10);//有参构造函数
Person p7 = Person(p6);//拷贝构造函数
Person(10);//称为匿名对象,创建了一个对象,但没有名
//放在等号右边,匿名对象就有名了,等号左边就是它的名
//匿名对象的特点,当前行执行结束后,系统会立即回收掉匿名对象
cout << "AAAAA" << endl;
//注意事项2
// 不要利用拷贝构造函数 初始化匿名对象
// 因为编译器会认为Person(p7);等价于Person p7;
// 无参构造一个p7对象,但是我们已经创建了
//Person(p7);//调用拷贝构造函数,初始化匿名对象
//3、隐式转换法
//直接写传参的数据
cout << "隐式转换法" << endl;
Person p8 = 10;//相当于写了Person p8=Person(10)
Person p9 = p8;//拷贝构造
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
3、拷贝构造函数的调用时机,什么时候会用到拷贝构造函数
#include<iostream>
using namespace std;
//拷贝构造函数的调用时机
class Person
{
public:
Person()
{
cout << "Person 构造函数的调用" << endl;
}
Person(int age)//有参构造函数
{
cout << "Person有参构造函数" << endl;
m_Age = age;
}
Person(const Person& p)//拷贝构造函数
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄" << p2.m_Age << endl;
}
//2、值传递的方式给函数传值
void doWork(Person p)//值传递的本质就是拷贝出来一个临时的副本
{
//会调用拷贝构造函数
}
void test02()
{
Person p;
doWork(p);
}
//3.值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int)&p1 << endl;
return p1;//局部变量,函数执行完之后,就会被释放掉
//以值的方式返回,不会返回p1,会拷贝出来一个新的返回
}
void test03()
{
Person p = doWork2();
cout << (int)&p << endl;
}
int main()
{
cout << "test01" << endl;
test01();
cout << "test02" << endl;
test02();
cout << "test03" << endl;
test03();
//说白了,拷贝构造函数的调用时机,一个是值的方式来传数据
//另一个是以值的方式返回,当用值的方式返回时,也会调用拷贝构造函数
system("pause");//按任意键继续
return 0;
}
/*
这里person p = doWork2()不会调用拷贝方法,因为编译器优化,会直接用对象p存放dowork2()的返回值。
这个问题在维基百科上叫做copy ellision。中文叫复制省略
*/
我们的代码和图片运行结果不一样,这是因为编译器的优化。
这里person p = doWork2()不会调用拷贝方法,因为编译器优化,会直接用对象p存放dowork2()的返回值。这个问题在维基百科上叫做copy ellision。中文叫复制省略。
4、构造函数的调用规则
4.1只要写了一个类,C++编译器都会给每个类至少添加三个函数
自己提供拷贝构造函数
#include<iostream>
using namespace std;
//构造函数的调用规则
//1、只要写了一个类,C++编译器都会给每个类至少添加三个函数,
// 不管你写不写这三个函数,写了就用你的,不写编译器自己提供
//默认构造函数(无参,空实现)
//析构函数(无参,空实现)
//拷贝构造函数(对属性进行值拷贝)
class Person
{
public:
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age)
{
cout << "Person 有参构造函数的调用" << endl;
m_Age = age;
}
Person(const Person& p)
{
cout << "Person 拷贝构造函数的调用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
};
void test01()
{
Person p1;
p1.m_Age = 18;
Person p2(p1);
cout << "p2的年龄为" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
当自己不提供默认构造函数时,程序也会提供
#include<iostream>
using namespace std;
//构造函数的调用规则
//1、只要写了一个类,C++编译器都会给每个类至少添加三个函数,
// 不管你写不写这三个函数,写了就用你的,不写编译器自己提供
//默认构造函数(无参,空实现)
//析构函数(无参,空实现)
//拷贝构造函数(对属性进行值拷贝)
class Person
{
public:
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age)
{
cout << "Person 有参构造函数的调用" << endl;
m_Age = age;
}
/*
Person(const Person& p)
{
cout << "Person 拷贝构造函数的调用" << endl;
m_Age = p.m_Age;
}
*/
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
};
void test01()
{
Person p1;
p1.m_Age = 18;
Person p2(p1);
cout << "p2的年龄为" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
4.2如果我们写了有参构造函数,编译器就不再提供默认构造函数,依然提供拷贝构造函数
#include<iostream>
using namespace std;
//构造函数的调用规则
//1、只要写了一个类,C++编译器都会给每个类至少添加三个函数,
// 不管你写不写这三个函数,写了就用你的,不写编译器自己提供
//默认构造函数(无参,空实现)
//析构函数(无参,空实现)
//拷贝构造函数(对属性进行值拷贝)
//2、如果我们写了有参构造函数,编译器就不再提供默认构造函数,
//依然提供拷贝构造函数
class Person
{
public:
Person(int age)
{
cout << "Person 有参构造函数的调用" << endl;
m_Age = age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
};
void test02()
{
//Person p;//会报错,因为我们提供了有参构造函数,
//那么编译器就不会在提供默认构造函数,我们也没提供默认构造函数,所以报错
Person p1(28);
Person p2(p1);
cout << "p2的年龄为" << p2.m_Age << endl;
}
int main()
{
test02();
system("pause");//按任意键继续
return 0;
}
4.3 如果我们写了拷贝构造函数,那么编译器就不再提供其他的构造函数
#include<iostream>
using namespace std;
//构造函数的调用规则
//1、只要写了一个类,C++编译器都会给每个类至少添加三个函数,
// 不管你写不写这三个函数,写了就用你的,不写编译器自己提供
//默认构造函数(无参,空实现)
//析构函数(无参,空实现)
//拷贝构造函数(对属性进行值拷贝)
//2、如果我们写了有参构造函数,编译器就不再提供默认构造函数,
//依然提供拷贝构造函数
//3.如果我们写了拷贝构造函数,那么编译器就不再提供其他的构造函数(默认、有参)
class Person
{
public:
Person(const Person& p)
{
cout << "Person 拷贝构造函数的调用" << endl;
m_Age = p.m_Age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
};
void test02()
{
//Person p;//会报错,因为我们提供了拷贝构造函数,
//那么编译器就不会在提供默认构造函数,我们也没提供默认构造函数,所以报错
//Person p1(28);//会报错,因为我们提供了拷贝构造函数,
//那么编译器就不会在提供有参构造函数,我们也没提供有参构造函数,所以报错
}
int main()
{
system("pause");//按任意键继续
return 0;
}
总结
5、深拷贝和浅拷贝
5.1浅拷贝
下面的代码会报错
#include<iostream>
using namespace std;
//深拷贝和浅拷贝
//
//
//编译器里提供的拷贝构造函数都是浅拷贝
class Person
{
public:
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height);//堆区开辟的数据由程序员手动开辟,
//也需要程序员手动释放,什么时候释放呢?就是在销毁前把它释放掉
//什么时候被销毁呢?
cout << "Person 有参构造函数的调用" << endl;
}
~Person()
{
//析构代码,将堆区开辟的数据做释放操作
if (m_Height != NULL)//如果它不空
{
delete m_Height;//释放它指向的堆区的内存
m_Height = NULL;//避免出现野指针,给它置空
}
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
int* m_Height;//用指针定义一个身高,因为我们要把身高的数据开辟到堆区
//new开辟的是内存,所以用指针
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" <<p1.m_Age<<"p1的身高为:"<<*p1.m_Height << endl;
Person p2(p1);//编译器里提供的拷贝构造函数都是浅拷贝
cout << "p2的年龄为:" << p2.m_Age << "p2的身高为:" << *p2.m_Height << endl;
//堆区开辟的数据由程序员手动开辟,
//也需要程序员手动释放,什么时候释放呢?就是在销毁前把它释放掉
//什么时候被销毁呢?
//局部变量,当函数执行完之后,p1,p2就销毁了,p1,p2销毁时调用析构函数
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
/*
拷贝函数是浅拷贝
栈区数据:先进后出
所以p2先执行析构函数
m_Height不空,释放它指向的堆区的内存
接下来是p1,它的指针不空,释放它指向的堆区的内存,
但这块内存已经被p2释放过了,再去释放就是非法操作了
这就是浅拷贝带来的问题,堆区数据被重复释放
*/
这是由于栈区数据:先进后出
所以p2先执行析构函数
m_Height不空,释放它指向的堆区的内存
接下来是p1,它的指针不空,释放它指向的堆区的内存,
但这块内存已经被p2释放过了,再去释放就是非法操作了
这就是浅拷贝带来的问题,堆区数据被重复释放
5.2深拷贝
浅拷贝问题要利用深拷贝解决,编译器提供的浅拷贝不太好使,那就自己写一个拷贝构造函数,使它指向一段新的堆区的数据
#include<iostream>
using namespace std;
//深拷贝和浅拷贝
//
//
//编译器里提供的拷贝构造函数都是浅拷贝
class Person
{
public:
Person()
{
cout << "Person 默认构造函数的调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height);//堆区开辟的数据(new一块出来内存)由程序员手动开辟,
//也需要程序员手动释放,什么时候释放呢?就是在销毁前把它释放掉
//什么时候被销毁呢?
cout << "Person 有参构造函数的调用" << endl;
}
//自己实现拷贝构造函数 解决浅拷贝带来的问题
Person(const Person &p)
{
cout << "Person 拷贝构造函数的调用" << endl;
/*
m_Age = p.m_Age;
m_Height = p.m_Height;
这是编辑器提供的拷贝构造函数写的内容
*/
m_Age = p.m_Age;
m_Height = new int(*p.m_Height);
}
~Person()
{
//析构代码,将堆区开辟的数据做释放操作
if (m_Height != NULL)//如果它不空
{
delete m_Height;//释放它指向的堆区的内存
m_Height = NULL;//给它置空,避免出现野指针
}
cout << "Person 析构函数的调用" << endl;
}
int m_Age;
int* m_Height;//用指针定义一个身高,因为我们要把身高的数据开辟到堆区
//new开辟的是内存,所以用指针
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" <<p1.m_Age<<"p1的身高为:"<<*p1.m_Height << endl;
Person p2(p1);//编译器里提供的拷贝构造函数都是浅拷贝
cout << "p2的年龄为:" << p2.m_Age << "p2的身高为:" << *p2.m_Height << endl;
//堆区开辟的数据由程序员手动开辟,
//也需要程序员手动释放,什么时候释放呢?就是在销毁前把它释放掉
//什么时候被销毁呢?
//局部变量,当函数执行完之后,p1,p2就销毁了,p1,p2销毁时调用析构函数
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题 。
6、初始化列表--给类中的属性进行初始化操作
#include<iostream>
using namespace std;
//初始化列表
class Person
{
public:
/*
//传统初始化操作
Person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
*/
//初始化列表初始化属性
Person(int a,int b,int c) :m_A(a), m_B(b), m_C(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);
cout << "m_A = " << p.m_A << " "
<< "m_B = " << p.m_B << " "
<< "m_C = " << p.m_C << endl;
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
7、类对象作为类成员(类中的成员可以是另一个类的对象)--构造和析构的顺序
#include<iostream>
using namespace std;
//一个类的对象作为另一个类的成员
//手机类
class Phone
{
public:
/*
Phone()
{
}
*/
Phone(string pName)
{
m_PName = pName;
cout << "Phone构造函数的调用" << endl;
}
~Phone()
{
cout << "Phone 析构函数的调用" << endl;
}
//手机品牌名称
string m_PName;
};
//人类
class Person
{
public:
Person(string name,string pName):m_Name(name), m_Phone(pName)
{
//m_Phone(pName);等价于
//Phone m_Phone=pName;隐式转换法
cout << "Person构造函数的调用" << endl;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
/*
错误代码,因为phone类中我们提供了有参构造函数,所以系统不会再提供
默认构造函数,那么Phone phone就不能写呀
Person(string name, Phone phone)
{
m_Name = name;
m_Phone = phone;
}
*/
/*
加个默认构造函数也可以这么赋值
Person(string name, Phone phone)
{
m_Name = name;
m_Phone = phone;
}
*/
//姓名
string m_Name;
//手机
Phone m_Phone;
};
//当其他类的对象作为本类的成员的时候
//那么创建本类的对象,会先构造其它类的对象,在构造自身
//先有人身上的属性(变量),不如胳膊腿,才能有这个人
//析构的顺序与构造相反
void test01()
{
//Phone ph("苹果");
//Person p("张三",ph);
Person p("张三", "苹果");
cout << p.m_Name << "拿着:" << p.m_Phone.m_PName << endl;
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
8、静态成员
8.1静态成员变量
8.1.1共享同一份内存,类外初始化
#include<iostream>
using namespace std;
//成员这个名词是出现在类中,不管是类中出现的函数也好,属性也好
//我们都成为成员,成员属性,成员函数
class Person
{
public:
//静态成员变量
//所有对象共享同一份数据,你改这个数了,那么别人再用的时候就是用的你改的数了
//编译阶段就分配内存,相当于程序还没运行之前,就已经分配内存了,在全局区里
//类内声明,类外必须初始化
static int m_A;
};
int Person::m_A = 100;//类外必须初始化,Person作用于下的
void test01()
{
Person p;
cout << p.m_A << endl;
Person p2;
p2.m_A = 200;
//输出100还是200?
cout << p.m_A << endl;//输出:200
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}
8.1.2静态成员变量有两种访问方式--通过对象进行访问,通过类名进行访问
#include<iostream>
using namespace std;
//成员这个名词是出现在类中,不管是类中出现的函数也好,属性也好
//我们都成为成员,成员属性,成员函数
class Person
{
public:
//静态成员变量
//所有对象共享同一份数据,你改这个数了,那么别人再用的时候就是用的你改的数了
//编译阶段就分配内存,相当于程序还没运行之前,就已经分配内存了,在全局区里
//类内声明,类外必须初始化
static int m_A;
//静态成员变量也是有访问权限的
private:
static int m_B;
};
int Person::m_A = 100;//类外必须初始化,Person作用于下的
int Person::m_B = 200;
void test02()
{
//静态成员变量 不属于某个对象, 所有对象都共享同一份数据
//因此静态成员变量有两种访问方式
//1、通过对象进行访问
Person p;
cout <<"通过变量进行访问" << p.m_A << endl;
//2、通过类名进行访问
cout <<"通过类名进行访问" << Person::m_A << endl;
//cout << "通过类名进行访问" << Person::m_B << endl;因为m_B是私有权限,类外访问不到
}
int main()
{
test02();
system("pause");//按任意键继续
return 0;
}
8.2静态成员函数--只能访问静态成员变量,访问方式有2种
#include<iostream>
using namespace std;
//静态成员函数
//所有对象都共享同一个函数
//静态成员函数只能访问静态成员变量
class Person
{
public:
//静态成员函数
static void fun()
{
m_A = 100;//静态成员函数可以访问静态成员变量
//m_B=200;//报错,静态成员函数只能访问静态成员变量
//因为无法区分到底是哪个对象的m_B
cout << "static void fun调用" << endl;
}
static int m_A;//静态成员变量
int m_B;//非静态成员变量
//静态成员函数也是有访问权限的
private:
static void fun2()
{
cout << "static void fun2调用" << endl;
}
};
int Person::m_A = 0;
void test01()
{
//静态成员函数的两种访问方式
//1、通过对象进行访问
Person p;
p.fun();
//2、通过类名访问
Person::fun();
//Person::fun2();//报错,类外访问不到私有的静态成员函数
}
int main()
{
test01();
system("pause");//按任意键继续
return 0;
}