往者不可谏,来者犹可追。
为有牺牲多壮志,敢教日月换新天
《C++文章汇总》
上一篇介绍了引用和汇编《010-菱形继承、虚继承、多继承作用和静态成员》,本文介绍const成员、拷贝构造函数、浅拷贝和深拷贝。
1.const成员
◼ const成员:被const修饰的成员变量、非静态成员函数
◼ const成员变量
必须初始化(类内部初始化),可以在声明的时候直接初始化赋值
非static的const成员变量还可以在初始化列表中初始化
◼ const成员函数(非静态)
const关键字写在参数列表后面,函数的声明和实现都必须带const
内部不能修改非static成员变量
内部只能调用const成员函数、static成员函数
class Car{
public:
const int m_price = 0;
Car():m_price(0){};
void run() const{
cout << "run()" << endl;
// test();//报错
}
void test(){
run();//可以调用
}
};
int main(){
Car car;
getchar();
return 0;
}
非const成员函数可以调用const成员函数
class Car{
public:
const int m_price = 0;
Car():m_price(0){};
void run() const{
cout << "run() const" << endl;
}
void run(){
cout << "run()" << endl;
}
};
int main(){
Car car;
getchar();
return 0;
}
const成员函数和非const成员函数构成重载
class Car{
public:
const int m_price = 0;
Car():m_price(0){};
void run() const{
cout << "run() const" << endl;
// test();//报错
}
void run(){
cout << "run()" << endl;
}
void test(){
run();//可以调用
}
};
int main(){
Car car;
car.run();
const Car car2;
car2.run();
return 0;
}
//输出
run()
run() const
非const对象(指针)优先调用非const成员函数
class Car{
public:
const int m_price = 0;
Car():m_price(0){};
void run(){
cout << "run()" << endl;
}
};
int main(){
Car car;
car.run();
const Car car2;
car2.run();//报错,不能调用
return 0;
}
const对象(指针)只能调用const成员函数、static成员函数
class Car{
public:
const int m_price = 0;
Car():m_price(0){};
void run() const{
cout << "run() const" << endl;
}
};
int main(){
Car car;
car.run();
const Car car2;
car2.run();
return 0;
}
const对象调用static成员函数
class Car{
public:
const int m_price = 0;
Car():m_price(0){};
void run() const{
cout << "run() const" << endl;
}
static void accelerate(){
cout << "accelerate()" << endl;
}
};
int main(){
Car car;
car.run();
const Car car2;
car2.run();
car2.accelerate();
return 0;
}
//输出
run() const
run() const
accelerate()
2.引用类型成员
◼ 引用类型成员变量必须初始化(不考虑static情况)
在声明的时候直接初始化
通过初始化列表初始化
class Car{
int age;
int &m_price = age;
public:
Car(int &price):m_price(price){}
};
3.拷贝构造函数
◼ 拷贝构造函数是构造函数的一种
◼ 当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化
◼ 拷贝构造函数的格式是固定的,接收一个const引用作为参数
using namespace std;
class Car {
int m_price;
int m_length;
public:
Car(int price = 0,int length = 0):m_price(price),m_length(length){
cout << "Car(int price = 0,int length = 0)" << endl;
}
//拷贝构造函数
Car(const Car &car){
cout << "Car(const Car &car)" << endl;
}
void display(){
cout << "price=" << m_price << "length=" << m_length << endl;
}
};
int main(){
Car car1;
Car car2(100);
Car car3(100,5);
//利用已经存在的car3对象,创建了一个car4新对象
//car4对象初始化时会调用拷贝构造函数
Car car4(car3);
getchar();
return 0;
}
//输出
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(const Car &car)
若没有写拷贝构造函数,相当于分别拷贝了car3对象的两个成员变量m_price,m_length
using namespace std;
class Car {
int m_price;
int m_length;
public:
Car(int price = 0,int length = 0):m_price(price),m_length(length){
cout << "Car(int price = 0,int length = 0)" << endl;
}
//拷贝构造函数
// Car(const Car &car){
// cout << "Car(const Car &car)" << endl;
// }
void display(){
cout << "price=" << m_price << "length=" << m_length << endl;
}
};
int main(){
Car car1;
Car car2(100);
Car car3(100,5);
//利用已经存在的car3对象,创建了一个car4新对象
//car4对象初始化时会调用拷贝构造函数
Car car4(car3);
//若没有写拷贝构造函数,相当于如下
/*
car4.m_price = car3.m_price;
car4.m_length = car3.m_length;
*/
car4.display();
getchar();
return 0;
}
//输出
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
price=100length=5
自己实现的拷贝构造函数并没有执行给成员变量赋值操作,需要自己实现赋值
using namespace std;
class Car {
int m_price;
int m_length;
public:
Car(int price = 0,int length = 0):m_price(price),m_length(length){
cout << "Car(int price = 0,int length = 0)" << endl;
}
//拷贝构造函数
Car(const Car &car){
cout << "Car(const Car &car)" << endl;
// m_price = car.m_price;
// m_length = car.m_length;
}
void display(){
cout << "price=" << m_price << "length=" << m_length << endl;
}
};
int main(){
Car car1;
Car car2(100);
Car car3(100,5);
//利用已经存在的car3对象,创建了一个car4新对象
//car4对象初始化时会调用拷贝构造函数
Car car4(car3);
//若没有写拷贝构造函数,相当于如下
/*
car4.m_price = car3.m_price;
car4.m_length = car3.m_length;
*/
car4.display();
getchar();
return 0;
}
//输出
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(const Car &car)
price=0length=0
实现赋值
using namespace std;
class Car {
int m_price;
int m_length;
public:
Car(int price = 0,int length = 0):m_price(price),m_length(length){
cout << "Car(int price = 0,int length = 0)" << endl;
}
//拷贝构造函数
Car(const Car &car){
cout << "Car(const Car &car)" << endl;
m_price = car.m_price;
m_length = car.m_length;
}
void display(){
cout << "price=" << m_price << "length=" << m_length << endl;
}
};
int main(){
Car car1;
Car car2(100);
Car car3(100,5);
//利用已经存在的car3对象,创建了一个car4新对象
//car4对象初始化时会调用拷贝构造函数
Car car4(car3);
//若没有写拷贝构造函数,相当于如下
/*
car4.m_price = car3.m_price;
car4.m_length = car3.m_length;
*/
car4.display();
getchar();
return 0;
}
//输出
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(int price = 0,int length = 0)
Car(const Car &car)
price=100length=5
赋值写成初始化列表
//拷贝构造函数
Car(const Car &car):m_price(car.m_price),m_length(car.m_length){
cout << "Car(const Car &car)" << endl;
// m_price = car.m_price;
// m_length = car.m_length;
}
若只初始化一个值,则未初始化的值为默认值0(在windows环境下为栈空间默认分配cc)
//拷贝构造函数
Car(const Car &car):m_price(car.m_price){
cout << "Car(const Car &car)" << endl;
// m_price = car.m_price;
// m_length = car.m_length;
}
//输出
Car(const Car &car)
price=100length=0
4.调用父类拷贝构造函数
I.若没有调用父类的拷贝构造函数,父类中的成员变量无法赋值为默认值(Mac中为0,Windows中为cc),但子类拷贝构造函数会调用父类的构造函数,因为子类的构造函数(包括拷贝构造函数)会默认调用父类无参的构造函数,此时会调用Person(int age = 0):m_age(age),age赋值为0
class Person{
public:
int m_age;
Person(int age = 0):m_age(age){
cout << "Person(int age = 0)" << age << endl;
}
Person(const Person &person):m_age(person.m_age){
cout << "Person(const Person &person):m_age(person.m_age)"<< person.m_age << endl;
}
};
class Student:public Person{
public:
int m_score;
Student(int age=0,int score=0):Person(age),m_score(score){
cout << "Student(int age=0,int score=0) age:"<< age << " score:"<< score << endl;
}
Student(const Student &student):m_score(student.m_score){
cout << "Student(const Student &student)"<< m_score << endl;
}
};
int main(){
Student stu1(18,100);
Student stu2(stu1);
cout << stu2.m_age << endl;
cout << stu2.m_score << endl;
getchar();
return 0;
}
//输出
Person(int age = 0)18
Student(int age=0,int score=0) age:18 score:100
Person(int age = 0)0//子类调用父类构造函数
Student(const Student &student)100
0
100
II.要想在子类拷贝构造函数中给子类Student中父类的成员变量m_age赋值,则在子类拷贝构造函数中调用父类的拷贝构造函数,因为父类指针可以指向子类对象
class Person{
public:
int m_age;
Person(int age = 0):m_age(age){
cout << "Person(int age = 0)" << age << endl;
}
Person(const Person &person):m_age(person.m_age){
cout << "Person(const Person &person):m_age(person.m_age)"<< person.m_age << endl;
}
};
class Student:public Person{
public:
int m_score;
Student(int age=0,int score=0):Person(age),m_score(score){
cout << "Student(int age=0,int score=0) age:"<< age << " score:"<< score << endl;
}
Student(const Student &student):Person(student),m_score(student.m_score){
cout << "Student(const Student &student)"<< m_score << endl;
}
};
int main(){
Student stu1(18,100);
Student stu2(stu1);
cout << stu2.m_age << endl;
cout << stu2.m_score << endl;
getchar();
return 0;
}
//输出
Person(int age = 0)18
Student(int age=0,int score=0) age:18 score:100
Person(const Person &person):m_age(person.m_age)18
Student(const Student &student)100
18
100
III.若自己不实现拷贝构造函数,则默认会将stu1中的所有成员变量的值都拷贝到stu2中
class Person{
public:
int m_age;
Person(int age = 0):m_age(age){
cout << "Person(int age = 0)" << age << endl;
}
// Person(const Person &person):m_age(person.m_age){
// cout << "Person(const Person &person):m_age(person.m_age)"<< person.m_age << endl;
// }
};
class Student:public Person{
public:
int m_score;
Student(int age=0,int score=0):Person(age),m_score(score){
cout << "Student(int age=0,int score=0) age:"<< age << " score:"<< score << endl;
}
// Student(const Student &student):Person(student),m_score(student.m_score){
// cout << "Student(const Student &student)"<< m_score << endl;
// }
};
int main(){
Student stu1(18,100);
Student stu2(stu1);
cout << stu2.m_age << endl;
cout << stu2.m_score << endl;
getchar();
return 0;
}
//输出
Person(int age = 0)18
Student(int age=0,int score=0) age:18 score:100
18
100
5.拷贝构造函数注意点
两个对象初始化后,car4 = car3不会调用拷贝构造函数,因为此时没有初始化新对象,car4和car3的内存空间已经分配完毕,car4=car3仅仅是简简单单的拷贝赋值操作
class Car {
int m_price;
int m_length;
public:
Car(int price=0,int length=0):m_price(price),m_length(length){
cout << "Car(int price=0,int length=0)" << endl;
}
Car(const Car &car):m_price(car.m_price),m_length(car.m_length){
cout << "Car(const Car &car)" << endl;
}
void display(){
cout << "price:" << m_price << " length:" << m_length << endl;
}
};
int main(){
Car car1(100,5);//Car(int price=0,int length=0)
Car car2(car1);//Car(const Car &car)
Car car3 = car2;//Car(const Car &car)
Car car4;//Car(int price=0,int length=0)
car4.display();//price:0 length:0
car4 = car3;//不会调用拷贝构造函数,因为此时不是在初始化新对象,仅仅是赋值操作
car4.display();//price:100 length:5
getchar();
return 0;
}
//输出
Car(int price=0,int length=0)
Car(const Car &car)
Car(const Car &car)
Car(int price=0,int length=0)
price:0 length:0
price:100 length:5
6.深拷贝和浅拷贝
◼编译器默认的提供的拷贝是浅拷贝(shallow copy)
将一个对象中所有成员变量的值拷贝到另一个对象
如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
可能会导致堆空间多次free的问题
◼如果需要实现深拷贝(deep copy),就需要自定义拷贝构造函数
将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
浅拷贝存在安全隐患
新建的car对象存在于堆空间,堆空间的指针指向存在于栈空间的name2地址,一旦main函数结束,name2所在的栈空间被释放,则堆空间的成员变量指向了一块被释放的栈空间区域,此块栈空间随时可能被覆盖,存在数据不安全
class Car{
int m_price;
char *m_name;
public:
Car(int price=0,char *name = NULL):m_price(price),m_name(name){}
void display(){
cout << "price is " << m_price << ",name is " << m_name << endl;
}
};
int main(){
// const char *name = "bmw";
char name2[] = {'b','m','w','\n'};
Car *g_car = new Car(100,name2);//传入name2,是一个栈空间的地址,堆空间的对象的成员变量m_name指向栈空间的地址,一旦函数结束栈空间被回收,指向一块不安全区域
g_car->display();
getchar();
return 0;
}
//输出
price is 100,name is bmw
在构造函数中开辟堆空间进行深拷贝,const参数能接收const和非const参数,非const参数只能接受非const参数,不管外面传入的是堆空间还是栈空间的地址,均重新申请堆空间保存不会存在数据安全问题
class Car{
int m_price;
char *m_name;
public:
Car(int price=0,const char *name = NULL):m_price(price){
if(name == NULL) return;
//申请新的堆空间
m_name = new char[strlen(name)+1]{};
//拷贝字符串数据到新的堆空间
strcpy(m_name, name);
}
~Car(){
if (m_name == NULL) return;
delete[] m_name;
m_name = NULL;
}
void display(){
cout << "price is " << m_price << ",name is " << m_name << endl;
}
};
int main(){
// const char *name = "bmw";
char name2[] = {'b','m','w','\n'};
Car *g_car = new Car(100,name2);//传入name2,是一个栈空间的地址,堆空间的对象的成员变量m_name指向栈空间的地址,一旦函数结束栈空间被回收,指向一块不安全区域
g_car->display();
getchar();
return 0;
}
//输出
price is 100,name is bmw
自己不实现拷贝构造函数,默认进行的赋值操作是浅拷贝,car1和car2的成员变量m_name都指向同一片内存空间,释放时这块空间会被释放两次,会崩溃pointer being freed was not allocated,malloc:*** set a breakpoint int malloc_error_break to debug,如何避免?自己实现深拷贝
class Car{
int m_price;
char *m_name;
public:
Car(int price=0,const char *name = NULL):m_price(price){
if(name == NULL) return;
//申请新的堆空间
m_name = new char[strlen(name)+1]{};
//拷贝字符串数据到新的堆空间
strcpy(m_name, name);
}
~Car(){
if (m_name == NULL) return;
delete[] m_name;
m_name = NULL;
}
void display(){
cout << "price is " << m_price << ",name is " << m_name << endl;
}
};
int main(){
Car car1(100,"bmw");
Car car2 = car1;
car2.display();
getchar();
return 0;
}
//输出
price is 100,name is bmw
浅拷贝(shallow copy):指针类型的变量只会拷贝地址值,深拷贝(deep copy):将指针指向的内容拷贝到新的存储空间
深拷贝:实现拷贝构造函数重新开辟内存空间进行深拷贝,若对象中的成员变量类型均是int类型,则不需要重写拷贝构造函数,默认int类型是直接将值拷贝过去,当成员变量有指针类型指向存储空间时才需要实现拷贝构造函数开辟空间将值拷贝过去
class Car{
int m_price;
char *m_name;
void copy(const char *name = NULL){
if(name == NULL) return;
//申请新的堆空间
m_name = new char[strlen(name)+1]{};
//拷贝字符串数据到新的堆空间
strcpy(m_name, name);
}
public:
Car(int price=0,const char *name = NULL):m_price(price){
copy(name);
}
Car(const Car &car):m_price(car.m_price){
copy(car.m_name);
}
~Car(){
if (m_name == NULL) return;
delete[] m_name;
m_name = NULL;
}
void display(){
cout << "price is " << m_price << ",name is " << m_name << endl;
}
};
int main(){
Car car1(100,"bmw");
Car car2 = car1;
car2.display();
getchar();
return 0;
}
//输出
price is 100,name is bmw
若成员变量是对象,则不需要重写拷贝构造函数进行深拷贝,相当于Person对象有两个int类型的成员变量
class Car{
int m_price;
}
class Person{
int m_age;
Car car;
};
int main(){
Person person1;
Person person2 = person1;
}
若成员变量是对象的指针,则需要重写拷贝构造函数进行深拷贝,也要根据需求定,若想两个Person对象拥有同一辆车,则无需进行深拷贝
class Car{
int m_price;
}
class Person{
int m_age;
Car *car;
};
int main(){
Person person1;
Person person2 = person1;
}