对象的初始化和清理
C++的面向对象来源于生活,因此每个对象也会有初始设置以及对象销毁前的清理数据的设置
1. 构造函数和析构函数
class Student
{
public:
//构建构造函数
Student()
{
}
//构建析构函数
~Student()
{
}
};
void test1()
{
Student stu1;
}
int main()
{
//调用test1,会在test1执行时自动调用类的构造函数和析构函数
test1;
/*
//若是直接在main()函数里面调用类,会先执行构造函数,等整个main()函数执行完毕再调用析构函数
Student stu1;
*/
return 0;
}
2. 构造函数的分类及调用
class Student
{
public:
//普通构造函数
//构建无参构造函数
Student()
{
cout << "调用无参构造函数" << endl;
}
//构建有参构造函数
Student(string Iname)
{
cout << "调用有参构造函数" << endl;
name = Iname;
}
//拷贝构造函数
Student( const Student &stu2 )
{
cout << "调用拷贝构造函数" << endl;
name = stu2.name;
}
//构建析构函数
~Student()
{
cout << "调用析构函数" << endl;
}
string name;
};
//构造函数调用
void test1()
{
//括号调用
Student stu1;//(调用普通无参构造函数/默认构造函数)
Student stu2("cara");//(调用普通有参构造函数)
Student stu3(stu2);//(调用拷贝构造函数)
cout << stu3.name << endl;
//显示调用
Student stu1;//(调用普通无参构造函数/默认构造函数)
Student stu2 = Student("cara");//(调用普通有参构造函数)
Student stu3 = Student(stu2);//(调用拷贝构造函数)
//匿名对象
Student("cara"); //匿名对象执行完毕后,系统会立即回收该匿名对象
//不要使用拷贝构造函数初始化匿名对象
Student(stu3); //这一句定价于:Student(stu3) == Student stu3,相当于是在创建对象,而不是在初始化对象
//隐式转换
Student stu2 = "cara";//(调用普通有参构造函数)
Student stu3 = stu2; //(调用拷贝构造函数)
}
3. 拷贝构造函数调用时机
4. 构造函数调用规则
5. 深拷贝与浅拷贝
编译器默认的拷贝函数是浅拷贝函数
class Student
{
public:
//构建无参构造函数
Student()
{
cout << "调用无参构造函数" << endl;
}
//构建有参构造函数
Student(int age,int Height)
{
cout << "调用有参构造函数" << endl;
mAge = age;
mHeight = new int(Height); //在堆区创建数据
}
//拷贝构造函数
Student( const Student &stu2 )
{
cout << "调用拷贝构造函数" << endl;
mAge = stu2.mAge;
//mHeight = stu2.mHeight; //编译器自动生成的是浅拷贝构造函数
mHeight = new int(*stu2.mHeight); //程序员自己构建的深拷贝构造函数,在堆区重新开辟一块内存空间
}
int mAge;
int* mHeight; //访问堆区的数据返回的是一个指针
//构建析构函数
~Student()
{
if (mHeight != 0)
{
delete mHeight;
mHeight = NULL;
}
cout << "调用析构函数" << endl;
}
};
//构造函数调用
void test1()
{
Student stu1;
Student stu2(18,180);
Student stu3(stu2);
}
int main()
{
test1();
return 0;
}
浅拷贝的一个问题:堆区内存的重复释放。
我们在堆区开辟一块内存用于存储stu2的身高,在栈区则存放着身高的内存地址。Student stu3(stu2)会将stu2的所有属性浅拷贝给stu3,包括年龄和身高的地址。根据栈区的释放规则(先进后出),stu3会首先执行析构函数,去释放堆区的内存空间。随后stu2再执行其析构函数,他会再去释放已经被释放过的堆区内存空间,因此会报错。
浅拷贝示意图:
深拷贝会在堆区重新开辟一块内存空间用于存储stu3的身高,因此不会出现重复释放的问题
深拷贝示意图:
6. 初始化列表
C++提供了初始化列表语法,用来初始化属性
//普通初始化操作方式
Student(int age,int Height)
{
mAge = age;
mHeight = Height;
}
//初始化列表操作方式
Student(int age, int Height): mAge(age), mHeight(Height) //或者:Student: mAge(10), mHeight(20)
{
}
7. 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
先调用A,再调用B
8. 静态成员
class Student
{
public:
static int a; //静态成员变量必须在类内进行声明
static void func()
{
a = 100; //静态成员函数只能访问静态成员变量,不能访问非静态成员变量
//因为静态成员函数是所有对象共享,而非静态成员变量是特定对象的属性,因此在静态成员函数内部访问非静态成员变量时,函数无法区分该
}
};
int Student::a = 0; //静态成员变量必须在类外进行初始化
C++对象模型和this指针
1. 成员变量和成员函数分开储存
只有非静态成员变量属于类的对象上,其他的(非静态成员函数、静态成员变量、静态成员函数) 都不属于。
空类所占内存为1,C++会为每一个空内存分配一个内存空间,以便于为彼此做区分。
2. this指针概念
class Student
{
public:
Student(int age)
{
this->age = age; //this指针用于将类的成员变量age与有参构造函数的形参age进行区分
}
Student& AgeAdd(Student stu1) //Student&返回的是对象本身,Student则返回的是值(值传递会调用拷贝构造函数,返回的是值的拷贝)
{
this->age += stu1.age;
return *this; //返回对象本身
}
int age;
};
//构造函数调用
void test1()
{
Student stu1(10);
Student stu2(10);
//链式编程思想
stu2.AgeAdd(stu1).AgeAdd(stu1).AgeAdd(stu1).AgeAdd(stu1)
}
3. 空指针访问成员函数
class Student
{
public:
void func1()
{
cout << "print 0" << endl;
}
void func2()
{
//因为我们创建的指针是一个空指针,因此无法指向特定的对象
//为了解决这种情况,可以加入如下代码段增加代码的鲁棒性
if (this == NULL)
{
return;
}
cout << "age is : " << age << endl; //这句话等价于cout << "age is : " << this->age << endl;
}
int age;
};
//构造函数调用
void test1()
{
Student* p = NULL;
p->func1();
p->func2(); //报错
}
int main()
{
test1();
return 0;
}
4. const修饰成员函数
class Student
{
public:
//每个成员函数默认都有this指针,他是一个指针常量,指针指向的方向不可改变
//函数后面加const,是对this指针进行修饰,this指针变成一个常量指针常量,其值和指向均不可进行修改
void func1() const
{
this->height = 180; //height前面加了mutable,是一个特殊的成员变量,可以对其值进行修改。
this->age = 20; //报错,age是普通成员函数,在常函数中不可对其值进行修改
}
void func2()
{
}
int age;
mutable int height;
};
//构造函数调用
void test1()
{
const Student stu1; //创建常对象
stu1.age = 10; //报错
stu1.height = 167; //height可修改
stu1.func1(); //可执行,func1()为常函数,可以被常对象调用
stu1.func2(); //报错,func2()并非常函数
}
int main()
{
test1();
return 0;
}