嵌入式开发学习 C++:day02

发布于:2025-09-02 ⋅ 阅读:(20) ⋅ 点赞:(0)

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)**和编译器/操作系统的内存管理机制有关,在实际开发中,绝不能依赖这种“不报错”的假象)。因为obj1obj2data指针指向同一块内存,析构时会对同一块内存释放两次,造成程序崩溃。

深拷贝
  • 定义不仅复制对象的表层数据,还会为指针成员重新分配堆内存,并复制原指针指向的内容。
  • **特点:**原对象和拷贝对象的指针成员指向不同的堆内存,彼此独立,修改其中一个不会影响另一个。
  • **实现方式:**需要手动重写拷贝构造函数和赋值运算符重载。

示例(修复浅拷贝问题):

/*************************************************************************
  > 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;
}

结果obj1obj2data指针指向不同的内存,修改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;
}

网站公告

今日签到

点亮在社区的每一天
去签到