目录
一、构造函数与析构函数
C++使用构造函数和析构函数实现类的初始化和清理功能。对于一个对象,C++编译器会自动调用构造函数和析构函数完成初始化和清理,若程序中未提供构造函数和析构函数,编译器会使用编译器提供的默认的构造函数和析构函数(均为空实现)。
1. 定义与语法
构造函数的作用是创建对象时给对象的成员属性赋值,其由编译器自动调用。
构造函数语法:类名(){}
- 构造函数没有返回值,也不写void
- 构造函数名称与类名相同
- 构造函数可以有参数,因此可以进行重载
- 程序调用对象时会自动调用构造函数,无需手动调用,且构造函数只调用一次
析构函数的作用是对象销毁前系统自动调用,执行对象销毁前的一些操作。
析构函数语法:~类名(){}
- 析构函数没有返回值,也不写void
- 析构函数名称与类名相同,但在名称前加~
- 析构函数不可以有参数,因此不可以进行重载
- 程序销毁对象时会自动调用析构函数,无需手动调用,且构造函数只调用一次
#include <iostream>
using namespace std;
class Circle {
public:
//构造函数
Circle() {
cout << "Circle的构造函数" << endl;
}
//析构函数
~Circle() {
cout << "Circle的析构函数" << endl;
}
//成员属性
double R;
//成员方法
double areaS() { //返回圆的周长
return 2 * 3.14 * R;
}
};
//创建函数测试构造函数
void test01(){
Circle C1; //使用函数生成存储于栈区的局部变量,便于查看析构函数的调用
}
int main() {
test01();
system("pause");
return 0;
}
Circle的构造函数
Circle的析构函数
请按任意键继续. . .
2. 构造函数的分类与调用
构造函数的分类:
- 按参数分为有参构造函数和无参构造函数,无参构造函数也称为默认构造函数
- 按类型分为普通构造函数和拷贝构造函数
构造函数的调用:
- 括号法
- 显示法
- 隐式转换法
注:
- 使用括号法调用默认构造函数时不能加小括号,因为添加小括号后编译器会将其辨认为函数的声明。
- 不要利用拷贝构造函数生成匿名对象,编译器会将其看作对象声明,从而产生重定义的错误
#include <iostream>
using namespace std;
class Circle {
public:
//无参构造函数
Circle() {
cout << "Circle的默认构造函数调用" << endl;
}
//有参构造函数
Circle(double a) {
R = a;
cout << "Circle的有参构造函数调用" << endl;
}
//拷贝构造函数
Circle(const Circle &C) {
R = C.R;
cout << "Circle的拷贝构造函数调用" << endl;
}
//析构函数
~Circle() {
cout << "Circle的析构函数" << endl;
}
//成员属性
double R;
//成员方法
double areaS() { //返回圆的周长
return 2 * 3.14 * R;
}
};
//创建函数测试构造函数
void test01(){
//构造函数的调用
//1.括号法
cout << "括号法调用构造函数" << endl;
Circle C1; // 默认构造函数的调用
Circle C2(3.14); //括号法调用有参构造函数
cout << "有参构造函数生成的圆C2的半径为:" << C2.R << endl;
Circle C3(C2); //括号法调用拷贝构造函数
cout << "拷贝构造函数生成的圆C3的半径为:" << C3.R << endl;
}
void test02() {
//构造函数的调用
//2.显示法
cout << "显示法调用构造函数" << endl;
Circle C1; // 默认构造函数的调用
Circle C2 = Circle(3.14); //括号法调用有参构造函数
cout << "有参构造函数生成的圆C2的半径为:" << C2.R << endl;
Circle C3 = Circle(C2); //括号法调用拷贝构造函数
cout << "拷贝构造函数生成的圆C3的半径为:" << C3.R << endl;
}
void test03() {
//构函数的调用
//3.隐式转换法,编译器会给转化成显示法进行操作
cout << "隐式转换法调用构造函数" << endl;
Circle C1; // 默认构造函数的调用
Circle C2 = 3.14; //括号法调用有参构造函数
cout << "有参构造函数生成的圆C2的半径为:" << C2.R << endl;
Circle C3 = C2; //括号法调用拷贝构造函数
cout << "拷贝构造函数生成的圆C3的半径为:" << C3.R << endl;
}
int main() {
test01();
cout << endl;
test02();
cout << endl;
cout << "生成匿名对象" << endl;
//生成匿名对象
Circle(10);
//Circle C(3.14); //生成一个半径为3.14的圆
//Circle(C); //使用拷贝构造函数生成匿名对象时编译器会报错,此时编译器会将代码 Circle(C); 看作 Circle C; 从而产生对C的重定义错误
cout << endl;
test03();
system("pause");
return 0;
}
括号法调用构造函数
Circle的默认构造函数调用
Circle的有参构造函数调用
有参构造函数生成的圆C2的半径为:3.14
Circle的拷贝构造函数调用
拷贝构造函数生成的圆C3的半径为:3.14
Circle的析构函数
Circle的析构函数
Circle的析构函数
显示法调用构造函数
Circle的默认构造函数调用
Circle的有参构造函数调用
有参构造函数生成的圆C2的半径为:3.14
Circle的拷贝构造函数调用
拷贝构造函数生成的圆C3的半径为:3.14
Circle的析构函数
Circle的析构函数
Circle的析构函数
生成匿名对象
Circle的有参构造函数调用
Circle的析构函数
隐式转换法调用构造函数
Circle的默认构造函数调用
Circle的有参构造函数调用
有参构造函数生成的圆C2的半径为:3.14
Circle的拷贝构造函数调用
拷贝构造函数生成的圆C3的半径为:3.14
Circle的析构函数
Circle的析构函数
Circle的析构函数
请按任意键继续. . .
3. 拷贝构造函数的调用时机
C++中拷贝构造函数的调用时机的三种情况:
- 使用一个已经创建完毕的对象来初始化新对象
- 值传递的方式给函数传值:实参通过值传递传值给形参时会拷贝实参的副本,此时会调用拷贝构造函数
- 以值方式返回局部对象
#include <iostream>
using namespace std;
class Circle {
public:
//无参构造函数
Circle() {
cout << "Circle的默认构造函数调用" << endl;
}
//有参构造函数
Circle(double a) {
R = a;
cout << "Circle的有参构造函数调用" << endl;
}
//拷贝构造函数
Circle(const Circle &C) {
R = C.R;
cout << "Circle的拷贝构造函数调用" << endl;
}
//析构函数
~Circle() {
cout << "Circle的析构函数" << endl;
}
//成员属性
double R;
};
void test01() {
//拷贝构造函数的调用时机
//1.使用一个已经创建完毕的对象初始化新对象时
Circle C1(3.14); //创建一个半径为3.14的圆C1,此时调用有参构造函数
Circle C2(C1); //使用C1初始化C2,此时会调用拷贝构造函数
}
void test02(Circle C) {
//拷贝构造函数的调用时机
//2.值传递的方式给函数传值时
}
Circle test03() {
//拷贝构造函数的调用时机
//3.值方式返回局部对象
Circle C1; //创建圆C1,此时调用默认构造函数
return C1; //值方式返回局部对象C1,此时会调用拷贝构造函数(通过创建局部对象的副本进行返回)
}
int main() {
//1.使用一个已经创建完毕的对象初始化新对象时
cout << "1.使用一个已经创建完毕的对象初始化新对象时" << endl;
test01();
cout << endl;
//2.值传递的方式给函数传值时
cout << "2.值传递的方式给函数传值时" << endl;
Circle C(3.14); //创建一个半径为3.14的圆C1,此时调用有参构造函数
test02(C); //值传递时编译器会创建实参的副本,此时会调用拷贝构造函数
cout << endl;
//3.值方式返回局部对象
cout << "3.值方式返回局部对象时" << endl;
Circle C2 = test03(); //值方式返回时,创建新的对象,调用拷贝构造函数
system("pause");
return 0;
}
1.使用一个已经创建完毕的对象初始化新对象时
Circle的有参构造函数调用
Circle的拷贝构造函数调用
Circle的析构函数
Circle的析构函数
2.值传递的方式给函数传值时
Circle的有参构造函数调用
Circle的拷贝构造函数调用
Circle的析构函数
3.值方式返回局部对象时
Circle的默认构造函数调用
Circle的拷贝构造函数调用
Circle的析构函数
请按任意键继续. . .
4. 构造函数调用规则
默认情况下,C++编译器至少会给一个类添加三个函数:
- 默认构造函数,无参,函数体为空
- 默认析构函数,无参,函数体为空
- 默认拷贝构造函数,对类的成员属性进行值拷贝
构造函数调用规则如下
- 若用户定义有参构造函数,则C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数
- 若用户定义了拷贝构造函数,则C++不再提供其它默认构造函数
5. 浅拷贝与深拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间进行拷贝操作
#include <iostream>
using namespace std;
class Person {
public:
//无参构造函数
Person() {
cout << "Person的默认构造函数调用" << endl;
}
//有参构造函数
Person(int age, int high) {
m_age = age;
m_high = new int(high); //将m_high存储到堆区
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &P) {
m_age = P.m_age;
//m_high = P.m_high; //浅拷贝的做法,直接将地址拷贝给新的对象。
//使用此方法,由于两个指针指向同一个堆区内存,在析构函数释放堆区内存时将会出现重复释放问题,造成程序错误
m_high = new int(*P.m_high); //深拷贝的做法,在堆区新开辟内存存放新对象该属性。此方法可解决浅拷贝带来的重复释放堆区内存问题
cout << "Person的拷贝构造函数调用" << endl;
}
//析构函数
~Person() {
//在对象即将被销毁前,使用析构函数释放创建对象时使用的堆区内存
if (m_high != NULL) {
delete m_high; //释放堆区内存
m_high = NULL; //将指针指向空,防止野指针的出现
}
cout << "Person的析构函数" << endl;
}
//成员属性
int m_age;
int * m_high;
};
void test() {
Person P1(10, 180); //创建对象
cout << "P1的年龄为:" << P1.m_age << "\tP1的身高为:" << *P1.m_high << endl;
Person P2(P1); //使用拷贝构造函数创建对象P2
cout << "P2的年龄为:" << P2.m_age << "\tP2的身高为:" << *P2.m_high << endl;
}
int main() {
test();
system("pause");
return 0;
}
Person的有参构造函数调用
P1的年龄为:10 P1的身高为:180
Person的拷贝构造函数调用
P2的年龄为:10 P2的身高为:180
Person的析构函数
Person的析构函数
请按任意键继续. . .
6. 初始化列表
在构造函数中使用初始化列表给对象属性赋值。
语法:构造函数():属性1(值1),属性2(值2)...{}
#include <iostream>
using namespace std;
class Person {
public:
//无参构造函数
Person() {
cout << "Person的默认构造函数调用" << endl;
}
//有参构造函数
Person(int age, int high):m_age(age),m_high(high) { //使用初始化列表进行对象属性赋值
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &P) {
m_age = P.m_age;
m_high = P.m_high;
cout << "Person的拷贝构造函数调用" << endl;
}
//析构函数
~Person() {
cout << "Person的析构函数" << endl;
}
//成员属性
int m_age;
int m_high;
};
void test() {
Person P1(10, 180); //创建对象
cout << "P1的年龄为:" << P1.m_age << "\tP1的身高为:" << P1.m_high << endl;
Person P2(P1); //使用拷贝构造函数创建对象P2
cout << "P2的年龄为:" << P2.m_age << "\tP2的身高为:" << P2.m_high << endl;
}
int main() {
test();
system("pause");
return 0;
}
Person的有参构造函数调用
P1的年龄为:10 P1的身高为:180
Person的拷贝构造函数调用
P2的年龄为:10 P2的身高为:180
Person的析构函数
Person的析构函数
请按任意键继续. . .
二、类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
#include <iostream>
using namespace std;
#include<string>
class Phone {
public:
//构造函数
Phone(string Name) :PName(Name) {
cout << "Phone的构造函数。" << endl;
}
//析构函数
~Phone() {
cout << "Phone的析构函数" << endl;
}
string PName;
};
class Person {
public:
Person(int age, int high, string PName) :m_age(age), m_high(high), m_Phone(PName) {
cout << "Person的构造函数。" << endl;
}
//析构函数
~Person() {
cout << "Person的析构函数" << endl;
}
//成员属性
int m_age;
int m_high;
Phone m_Phone;
};
void test() {
Person P1(10, 180,"IPhone"); //创建对象
cout << "P1的年龄为:" << P1.m_age << "\tP1的身高为:" << P1.m_high << "\t手机品牌:" << P1.m_Phone.PName << endl;
}
int main() {
test();
system("pause");
return 0;
}
Phone的构造函数。
Person的构造函数。
P1的年龄为:10 P1的身高为:180 手机品牌:IPhone
Person的析构函数
Phone的析构函数
请按任意键继续. . .
注:若B类的对象作为A类的成员,则在实例化A类的某一个对象时会先进行实例化B类的操作再执行实例化A类的操作。
三、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。静态成员分为:
1. 静态成员变量
- 静态成员变量不属于某个成员变量,所有对象共享同一份数据
- 在编译阶段分配内存
- 类内定义,类外初始化
静态成员变量的访问方式
- 通过对象进行访问
- 通过类名进行访问
注:
- 静态成员变量具有访问权限,私有权限在类外无法访问
- 静态成员变量必须在类外进行初始化
#include <iostream>
using namespace std;
#include<string>
class Student {
public:
//构造函数
Student() {
cout << "Person的构造函数。" << endl;
}
//析构函数
~Student() {
cout << "Person的析构函数" << endl;
}
//成员属性
string m_Name;
string m_ID;
double m_Score;
static int SNumber; //类内定义静态成员变量
};
int Student::SNumber = 0; //类外对静态成员变量初始化
void test() {
Student S1;
cout << "通过对象名访问的S1中的SNumber值为:" << S1.SNumber << endl;
cout << "通过类名称访问静态成员变量SNumber的值为:" << Student::SNumber << endl;
S1.SNumber = 10; //通过S1修改静态成员变量SNumber
Student S2;
cout << "S2中的SNumber值为:" << S2.SNumber << endl;
}
int main() {
test();
system("pause");
return 0;
}
Person的构造函数。
通过对象名访问的S1中的SNumber值为:0
通过类名称访问静态成员变量SNumber的值为:0
Person的构造函数。
S2中的SNumber值为:10
Person的析构函数
Person的析构函数
请按任意键继续. . .
2. 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
注:
- 静态成员函数只能访问静态成员变量,不能访问其它变量
- 静态成员函数具有访问权限,私有权限的静态成员函数不能在类外访问
#include <iostream>
using namespace std;
#include<string>
class Student {
public:
//构造函数
Student() {
cout << "Person的构造函数。" << endl;
}
//静态成员函数
static void number(int n) {
SNumber = n; //静态成员函数只能访问静态成员变量
//m_Name = "Zhangsan"; //报错,非静态成员引用必须与特定对象相对
}
//析构函数
~Student() {
cout << "Person的析构函数" << endl;
}
//成员属性
string m_Name;
string m_ID;
double m_Score;
static int SNumber; //类内定义静态成员变量
};
int Student::SNumber = 0; //类外对静态成员变量初始化
void test() {
Student S1;
S1.number(10); //通过特定对象方法静态成员函数
cout << "通过实例化的对象调用静态成员函数修改静态成员变量SNumber的值:" << S1.SNumber << endl;
Student::number(100); //通过类名访问静态成员函数
cout << "通过类名调用静态成员函数修改静态成员变量SNumber的值:" << Student::SNumber << endl;
}
int main() {
test();
system("pause");
return 0;
}
Person的构造函数。
通过实例化的对象调用静态成员函数修改静态成员变量SNumber的值:10
通过类名调用静态成员函数修改静态成员变量SNumber的值:100
Person的析构函数
请按任意键继续. . .
四、C++对象成员与this指针
1. 成员变量和成员函数分开存储
成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
注:C++编译器会为每个空对象分配一个字节内存空间,以区分空对象占内存的位置,即每个空对象也有一个独一无二的内存地址。
#include <iostream>
using namespace std;
#include<string>
class Student1 {
};
class Student2 {
int test1;
};
class Student3 {
int test1;
static int test2;
};
class Student4 {
int test1;
static int test2;
void fun() {
cout << test1 << endl;
}
};
class Student5 {
int test1; //非静态成员变量属于类的对象上
static int test2; //静态成员变量不属于类的对象上
void fun() { //非静态成员函数不属于类的对象上
cout << "fun函数" << endl;
}
static void fun1() { //静态成员函数不属于类的对象上
cout << "fun1函数" << endl;
}
};
void test() {
Student1 S1;
cout << "空对象所占内存空间:" << sizeof(S1) << endl; //空对象占一个字节内存
Student2 S2;
cout << "具有一个int成员变量的对象所占内存空间:" << sizeof(S2) << endl; //非空对象所占内存是非静态成员变量所占空间大小
Student3 S3;
cout << "具有一个int成员变量和一个静态成员变量的对象所占内存空间:" << sizeof(S3) << endl; //静态成员变量不占具体类对象的内存空间
Student4 S4;
cout << "具有一个int成员变量和一个静态成员变量和非静态成员函数的对象所占内存空间:" << sizeof(S4) << endl; //非静态成员函数不占具体类对象的内存空间
Student5 S5;
cout << "具有一个int成员变量和一个静态成员变量和非静态成员函数和一个静态成员函数的对象所占内存空间:" << sizeof(S5) << endl; //静态成员函数不占具体类对象的内存空间
}
int main() {
test();
system("pause");
return 0;
}
空对象所占内存空间:1
具有一个int成员变量的对象所占内存空间:4
具有一个int成员变量和一个静态成员变量的对象所占内存空间:4
具有一个int成员变量和一个静态成员变量和非静态成员函数的对象所占内存空间:4
具有一个int成员变量和一个静态成员变量和非静态成员函数和一个静态成员函数的对象所占内存空间:4
请按任意键继续. . .
2. this指针概念
this指针是C++提供的特殊的对象指针,其指向被调用的成员函数所述的对象。
- this指针是隐含在每一个非静态成员函数内部的一种指针,在非静态成员函数中可以直接使用。
- this指针不需要定义,可以直接使用。
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区别
- 在类的非静态成员函数中返回对象本身,可使用return *this
#include <iostream>
using namespace std;
#include<string>
class Student {
public:
void setAge(int age) {
//this指针指向的是被调用的成员函数所属的对象
this->age = age; //在非静态成员函数中使用this指针解决变量名称冲突
}
Student& addAge(Student &S) {
this->age += S.age;
return *this; //返回的是被调用的成员函数所属对象的自身
}
int age;
};
void test() {
//this指针的用途
//1.解决变量名称冲突
Student S1;
S1.setAge(10); //在非静态成员函数setAge中的this指针指向S1
cout << "S1的年龄为:" << S1.age << endl;
//2.返回对象本身,使用*this
Student S2;
S2.setAge(20);
//链式编程思想,通过this指针S2.addAge(S1)返回的仍是S2
S2.addAge(S1).addAge(S1).addAge(S1);
cout << "S2的年龄为:" << S2.age << endl;
}
int main() {
test();
system("pause");
return 0;
}
S1的年龄为:10
S2的年龄为:50
请按任意键继续. . .
3. 空指针访问成员函数
C++中空指针也可以调用成员函数,但要注意调用的成员函数不能使用this指针。
注:类成员函数访问类成员变量时默认会使用this指针,即在类成员函数中有:类成员变量 等价于 this->类成员变量。因此,空指针访问成员函数时,成员函数不能使用成员变量,否则程序会崩溃。
#include <iostream>
using namespace std;
#include<string>
class Student {
public:
void showClassname() {
cout << "This is the function showClassname." << endl;
}
void showStudentAge() {
// 判断类对象是否为空,增加程序的健壮性。
if (this == NULL) { //若没有这些代码,空指针对象调用该成员函数程序将会崩溃
cout << "该对象的指针是空指针" << endl;
return;
}
cout << "The student's age is "<< age << endl; //当成员函数访问类成员变量时,空指针对象不能访问该成员函数
}
int age;
};
void test() {
Student *p = NULL; //定义一个类对象空指针
p->showClassname(); //类成员函数不访问类成员变量时,能够正常访问
p->showStudentAge(); //类成员函数访问类成员变量时,空指针对象不能正常访问,增加判断指针是否为空的程序段可以增加程序健壮性
}
int main() {
test();
system("pause");
return 0;
}
This is the function showClassname.
该对象的指针是空指针
请按任意键继续. . .
4. const修饰成员函数
常函数
- 成员函数后加const修饰后我们称该函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
#include <iostream>
using namespace std;
#include<string>
class Student {
public:
void showStudent() const { //常函数,在成员函数后加const本质上修饰的是this指针
//this指针的本质是指针常量,相当于 Student * const this, 在成员函数后加const相当于const Student * const this
//因此此时成员变量的值不可以在常函数中进行修改
// age = 100; //常函数中不可修改
score = 99; // mutable修饰的成员变量在常函数中也可修改
}
void fun() {
}
int age;
mutable int score; //在成员变量前加mutable,使其在常函数中也可以修改
};
void test() {
const Student S1; //常对象
//S1.age = 20; //常对象不可以修改对象成员变量
S1.score = 88; //mutable修饰的成员变量可以通过常对象进行修改
//S1.fun(); //常对象不能调用普通成员函数,因为普通成员函数可以修改普通成员变量
S1.showStudent(); //常对象只可以访问常函数
}
int main() {
test();
system("pause");
return 0;
}