C++:第03天笔记
对象的初始化和清理
函数重载:同一作用域下,函数名必须相同,参数列表必须不同(参数个数、参数类型、参数数据)
构造函数的分类以及调用
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转化法
案例:
/*************************************************************************
> File Name: demo01.cpp
> Author: 小刘
> Description: 构造函数的分类及调用
> Created Time: 2025-08-20 08:08:43
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
/**
* 1.构造函数分类
* 按照参数分类:有参构造和无参构造 无参又称为默认构造
* 按照类型分为:普通构造和拷贝构造
*/
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;
}
public :
int age;
};
/**
* 2.构造函数的调用
* 调用无参构造函数
*/
void test01()
{
Person p; // 调用无参构造函数
}
// 调用有参构造函数
void test02()
{
// 2.1 括号法:常用
Person(10);
// 注意:调用无参构造函数不能加括号,如果家里编译器认为这是一个函数声明
// Person p1()
//2.2 显示法
Person p2 = Person(10); // 有参构造
Person p3 = Person(p2); // 有参构造+拷贝构造
// Person(10) 单独写就是匿名对象,当前执行结束,马上析构
//2.3 隐式转换法
Person p4 = 10; // 等价于 Person p4 = Person(10);
Person p5 = p4; // 等价于 Person p5 = Person(p4);
// 注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为对象声明
// Person p5(p4);
}
int main()
{
test01();
test02();
system("pause");
return 55;
}
函数重载:同一做用域下,函数名必须相同,参数列表必须不同(参数个数、参数类型、参数顺序)
拷贝构造函数的调用时机
C++中拷贝构造函数调用时机通常由三种情况:
①使用一个已经创建完毕的对象来初始化一个新对象
②值传递的方式给函数参数串值
③以值方式返回局部对象
示例
/*************************************************************************
> File Name: demo02.cpp
> Author: 小刘
> Description: 构造函数调用时机
> Created Time: 2025-08-20 08:25:26
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
/**
* 创建一个Person类
* 类不占用内存
*/
class Person
{
public :
Person()
{
cout << "无参构造函数" << endl;
}
Person(int age)
{
mAge = age;
cout << "有参构造函数" << endl;
}
// 以上都是普通构造
Person(const Person& p)
{
mAge = p.mAge;
cout << "拷贝构造函数" << endl;
}
// 析构函数在释放内存之前调用
// 析构函数不支持重载,析构只有无参函数
~Person()
{
cout << "析构函数" << endl;
}
public:
int mAge;
};
//1.使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person oldman; // 调用无参构造函数
Person man(100); // man对象已经创建完毕
Person newman(man); // 调用拷贝构造函数
Person newman2 = man; // 调用拷贝构造函数
// Person newman3;
// newman3 = man; // 不是调用,这是一个赋值操作
}
// 2.值传递的方式给函数参数传值
// 相当于Person p1 = p;
void dowork(Person p1)
{
}
void test02()
{
Person p; // 无参构造函数
dowork(p);
}
// 3.以值方式返回局部对象
Person dowork2()
{
Person p1;
cout << (int*)&p1 << endl;
return p1; // 返回局部对象
}
// 相当于 Person p = p1;
void test03()
{
Person p = dowork2();
cout << "text03 函数" << (int*)&p << endl;
}
int main()
{
test01();
test02();
test03();
return 0;
}
构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数
①默认构造函数(无参,函数体为空)
②默认析构函数(无参,函数体为空)
③默认拷贝构造函数(对属性进行值拷贝)
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造(用户定义有参构造,会覆盖掉默认的无参构造)
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
案例:
/*************************************************************************
> File Name: demo02.cpp
> Author: 小刘
> Description: 构造函数调用时机
> Created Time: 2025-08-20 08:25:26
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
/**
* 创建一个Person类
* 类不占用内存
*/
class Person
{
public :
Person()
{
cout << "无参构造函数" << endl;
}
Person(int age)
{
mAge = age;
cout << "有参构造函数" << endl;
}
// 以上都是普通构造
Person(const Person& p)
{
mAge = p.mAge;
cout << "拷贝构造函数" << endl;
}
// 析构函数在释放内存之前调用
// 析构函数不支持重载,析构只有无参函数
~Person()
{
cout << "析构函数" << endl;
}
public:
int mAge;
};
//1.使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(18);
// 如果不写拷贝构造,编译器会自动添加拷贝,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.mAge << endl;
}
void test02()
{
// 如果用户提供有参构造,编译器不会提供默认(无参)构造函数
Person p1; // 此时如果用户自己没有提供默认构造,会出错,报错原因,自定义有参构造覆盖掉了默认无参构造
// 以后养成一个习惯:如果定义有参,一定要同时定义一个无参
Person p2(10); // 用户提供的有参构造函数会提供
// 如果用户提供拷贝构造,编译器不会提供其他构造
Person p4; // 此时如果用户没有提供默认构造,会出错
Person p5; // 此时如果用户没有提供有参构造,会出错
Person p6(p5); // 用户自己提供拷贝构造
}
// 3.以值方式返回局部对象
Person dowork2()
{
Person p1;
cout << (int*)&p1 << endl;
return p1; // 返回局部对象
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
深拷贝与浅拷贝
深拷贝是面试经典问题,也是常见的一个坑。
在C++中,深拷贝和浅拷贝是处理对象复制时的两种方式,核心区别在于是否对指针成员指向的堆内存进行复制。
浅拷贝(Shallow Copy)
- 定义:仅复制对象的表层数据(如基本类型成员),对于指针成员,只复制指针本身的地址,而不复制指针指向的堆内存。
- 特点:原对象和拷贝对象的指针成员会指向同一块堆内存。
- 默认行为:C++编译器生成的默认拷贝构造函数和赋值运算符重载都是浅拷贝。
/*************************************************************************
> File Name: demo04.cpp
> Author: 小刘
> Description: 浅拷贝
> Created Time: 2025-08-20 11:00:58
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
// 定义一个myclass类
class MyClass
{
private : // 权限访问修饰符 私有权限
int* data; // 指针成员
public:
MyClass()
{
}
// 构造函数,分配堆内存
MyClass(int value)
{
data = new int(value); // 通过int分配的内存叫做 自由存储区
cout << "构造函数:分配内存" << endl;
}
// 析构函数:释放内存
~MyClass()
{
delete data; // 释放指针指向的内存
cout << "析构函数:释放内存" << endl;
}
};
int main(int argc,char* argv[])
{
MyClass obj;
MyClass obj1(10);
MyClass obj2 = obj1; // 等价于MyClass obj2 = MyClass(Obj1); 使用默认的拷贝函数,默认拷贝是浅拷贝
return 0;
}
问题:上述代码理论上会导致double free 错误(但实际运行时可能不报错,主要原因与C++的**未定义行为(UndefinedBehavior)**和编译器/操作系统的内存管理机制有关,在实际开发中,绝不能依赖这种“不报错”的假象)。因为obj1
和obj2
的data
指针指向同一块内存,析构时会对同一块内存释放两次,造成程序崩溃。
深拷贝
- 定义不仅复制对象的表层数据,还会为指针成员重新分配堆内存,并复制原指针指向的内容。
- **特点:**原对象和拷贝对象的指针成员指向不同的堆内存,彼此独立,修改其中一个不会影响另一个。
- **实现方式:**需要手动重写拷贝构造函数和赋值运算符重载。
示例(修复浅拷贝问题):
/*************************************************************************
> File Name: demo05.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-20 12:15:59
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
class MyClass
{
private:
int *data; // 指针成员
public:
void setValue(int value)
{
*data = value;
}
int getvalute()
{
return *data;
}
public:
// 有参改造函数
MyClass(int value)
{
data = new int(value);
cout << "构造函数,分配内存" << endl;
}
// 深 拷贝构造函数
MyClass(const MyClass& other)
{
data = new int(*other.data); // 复制内容,而非地址(申请新内存存放拷贝的堆数据)
cout << "深拷贝构造函数:复制内容" << endl;
}
// 深拷贝的赋值运算符(赋值运算符重载,扩展)
MyClass& operator = (const MyClass& other)
{
if(this != &other) // 避免自赋值
{
delete data; // 释放当前内存
data = new int(*other.data); //赋值新内容
cout << "深拷贝赋值:复制内存" << endl;
}
return *this; // this后面讲
}
~MyClass()
{
delete data; // 释放自身的指针成员
cout << "析构函数:释放内存" << endl;
}
int a =10;
int b = a;
};
int main(int argc ,char *argv[])
{
MyClass obj1(10); // 调用有参构造函数
MyClass obj2 = obj1; // 调用深拷贝构造函数
obj2.setValue(20); // 修改obj2的数据
cout << "obj1的值:" << obj1.getvalute() << endl;
cout << "obj2的值:" << obj2.getvalute() << endl;
return 0;
}
结果:obj1
和obj2
的data
指针指向不同的内存,修改obj2
不会影响obj1
,析构时也不会出现重复释放的问题。
何时需要深拷贝?
当类中包含指针成员且指针指向堆内存时,必须使用深拷贝,否则会导致:
- 重复释放内存(double free)
- 一个对象修改数据影响另一个对象
- 程序崩溃或内存泄漏
总结
类型 | 复制内容 | 指针成员行为 | 适用场景 |
---|---|---|---|
深拷贝 | 仅表层数据(如基本类型) | 共享一块堆内容 | 无指针成员或指针指向常量时 |
浅拷贝 | 表层数据+指针指向的内容 | 各自拥有独立的堆内存 | 包含指针成员且指向堆内存时 |
在实际开发中,若类包含动态内存分配(如new
操作),务必手动实现深拷贝(自己提供拷贝构造函数),避免默认浅拷贝带来的问题。
初始化列表
作用:
C++提供了初始化列表的语法,用来初始化对象的属性。
语法:
构造函数():属性1(值1),属性2(值2)...{}
示例:
/*************************************************************************
> File Name: demo06.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-20 15:03:51
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
class Person {
private:
int m_A;
int m_B;
int m_C;
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){}
void printfPerson()
{
cout << "m_A = " << m_A << endl;
cout << "m_B = " << m_B << endl;
cout << "m_C = " << m_C << endl;
}
};
int main(int argc, char *argv[])
{
Person person(11,12,13);
person.printfPerson();
system("pause");
return 0;
}
类对象做为成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
class A{}
class B {
A a; // 使用A类创建的对象a就是类B的成员
}
B类中有对象A作为成员,A为对象成员。
那么当创建B对象,A与B的构造和析构的顺序谁先谁后?
/*************************************************************************
> File Name: demo07.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-20 15:18:13
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
// 创建类Phone
class Phone
{
public:
Phone(string name)
{
m_name = name;
cout << "phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_name;
};
// 创建类Person
class Person
{
public:
Person(string name,string pname):m_name(name),m_phone(pname)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
void playGame()
{
cout << m_name << "使用" << m_phone.m_name << "牌手机" << endl;
}
string m_name;
Phone m_phone; // 类对象作为成员
};
void test01()
{
// 构造的顺序是:先调用对象成员构造,在调用本类构造
Person p("张三","OPPO x1");
p.playGame();
}
int main(int argc ,char *argv[])
{
test01();
system("pause");
return 0;
}
静态成员
静态成员就是在成员变量和成员函数前加上关键字static
,称之为静态成员。
静态成员分为:
- 静态成员变量
- 所有对象共用一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
**示例1:**静态成员变量
/*************************************************************************
> File Name: demo08.cpp
> Author: 小刘
> Description: 静态成员变量
> Created Time: 2025-08-20 15:44:04
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
class Person
{
public :
static int m_A; // 静态成员变量
// 静态成员变量
// 1.在编译阶段分配内存,普通成员在运行阶段分配内存
// 2.类内声明,类外初始化
// 3.所有成员共享同一份数据
private :
static int m_B;
};
// 类外初始化
int Person::m_A = 10;
int Person::m_B = 10;
int main(int argc ,char *argv[])
{
// 静态成员变量两种访问方式
// 1.通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A:" << p1.m_A << endl; // p1.m_A :100
Person p2;
p2.m_A = 200;
cout << "p1.m_A:" << p1.m_A << endl; // p1.m_A :200
cout << "p2.m_A:" << p2.m_A << endl; // p2.m_A :200
// 2.通过类名
cout << "m_A:" << Person::m_A << endl; // m_A :200
// cout << "m_B:" << Person::m_B << endl; // 私有权限访问不到
system("pause");
return 0;
}
**示例:**静态成员函数
/*************************************************************************
> File Name: demo09.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-20 14:57:17
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
class Person
{
public :
// 静态成员变量
// 1.在编译阶段分配内存,普通成员在运行阶段分配内存
// 2.类内声明,类外初始化
static void func()
{
cout << "func调用" << endl;
m_A = 100;
// m_B = 100 // 错误:不可以访问非静态成员变量
}
static int m_A;
int m_B ;
private :
// 静态成员函数也是有访问权限
static void fun2()
{
cout << "func2调用" << endl;
}
};
// 类外初始化
int Person::m_A = 10;
void test01()
{
// 静态成员变量两种访问方式
// 1.通过对象
Person p1;
p1.func();
// 2.通过类名
Person ::func();
// Person::func2(); // 私有权限访问不到
}
int main(int argc ,char *argv[])
{
test01();
system("pause");
return 0;
}
对象模型和this指针
成员变量和成员函数分开储存
在C++中,类内存成员变量和成员函数分开存储。
只有非静态成员变量属于类的对象上。
class Person
{
public:
Person()
{
mA = 0;
}
// 非静态成员变量占对象空间
int mA;
// 静态成员不占对象的空间
static int mB;
// 函数也不占对象空间,所有函数共享一个函数示例
void fun()
{
cout << "mA" << this -> mA << endl;
}
// 静态成员函数也不占用对象空间
static void sfunc()
{
}
};
this指针概念
通过上面的知识,我们知道在C++中成员变量和成员函数时分开存储的。
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码。
那么问题是:这块代码是如何区分哪个对象调用自己的呢?
C++提供了一个特殊的对象指针this 指针
,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针。
this指针不需要定义,直接使用即可。
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数返回对象本身,可使用
return *this
示例:
/*************************************************************************
> File Name: demo10.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-20 16:49:20
************************************************************************/
#include <iostream>
#include <iomanip>
using namespace std;
class Person
{
public:
Person(){}
Person(string name,int age)
{
// 当形参和成员变量同名时,可用this指针来区分
this-> name = name; // this -> name 访问当前的 成员变量name
this -> age = age ; //
}
Person& personAddPerson(Person p)
{
this->age += p.age; // //等价于 this->age = this->age + p.age;
return *this; // 解引用指针得到对象
}
public:
string name;
int age;
};
void test01()
{
Person p1("张三",21);
cout << "p1.name = " << p1.name << ",p1.age = " << p1.age << endl;
Person p2("李四",25);
p2.personAddPerson(p1).personAddPerson(p1).personAddPerson(p1);//链式调用 如何实现链式? 让自身的成员函数返回对象指针即可*this
cout << "p.age = " << p2.age << endl;
}
int main(int argc ,char *argv[])
{
test01();
system("pause");
return 0;
}
空指针访问成员函数
C++中空指针也是可以调用成员函数,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
示例:
/*************************************************************************
> File Name: demo10.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-29 17:03:34
************************************************************************/
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
// 空指针访问成员函数
class Person
{
public:
void showClassName()
{
cout << "我是Person类!" << endl;
}
void showPerson()
{
// 开发中,我们对对象做非空校验
if(this == NULL)
{
return;
}
cout << mAge << endl;
}
public:
int mAge ;
};
void test01()
{
Person *p = NULL;
p->showClassName(); // 空指针,可以调用成员函数
p->showPerson(); // 但是如果成员函数中调用了this指针,就不可以了
}
int main(int argc ,char *argv[])
{
test01();
system("pause");
return 0;
}
coust修饰成员函数 [了解]
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数内不可修改成员属性
- 成员属性声明时加关键字
muable
后,在常函数中依然可以修改
常对象
- 声明对象前const称该对象为常对象
- 常对象只能调用常函数
示例:
/*************************************************************************
> File Name: demo11.cpp
> Author: 小刘
> Description:
> Created Time: 2025-08-29 18:23:58
************************************************************************/
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
class Person
{
public:
Person()
{
m_A = 0;
m_b = 0;
}
// this指针的本质是一个指针常量,指针的指向不可修改
// 如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const
{
// const Type* const pointer;
// this = NULL;
//this -> mA = 100; // 但this指针指向对象的数据是可以修改的
// const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_b = 100;
}
void Myfunc() const{
}
public:
int m_A;
mutable int m_b; // 可修改,可变的
};
// const修改对象 常对象
void test01()
{
const Person person; // 常量对象
cout << person.m_A << endl;
// person.mA = 100; // 常对象不能修改成员变量值,但是可以访问
person.m_b = 200; // 但是常对象可以修改mutable修饰的成员变量
// 常对象访问成员函数
person.Myfunc(); // 常对象不能调用const的函数
}
int main(int argc ,char *argv[])
{
test01();
system("pause");
return 0;
}