【C++】类和对象下

类和对象(下)

一、构造函数(初始化列表)

初始化成员有两种方式:

  1. 使用函数体内赋值
  2. 初始化列表
    之前我们实现构造函数时,初始化成员变量主要是使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表。

初始化列表的特点:

  • 初始化列表的使用方式:以一个冒号开始,接着是以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式:_year(year) ,_month(month) ,_day(day)
  • 每个成员变量,const变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则编译器报错
  • C++11支持成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用
  • 尽量使用初始化列表初始化,因为那些你不再初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器。对于显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造函数会编译错误
  • 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表的先后顺序无关。建议声明顺序和初始化列表顺序保持一致

初始化列表总结:

无论是否显示写初始化列表,每个构造函数都有初始化列表

无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}

private:
	int _hour;
};

class Date
{
public:
	Date(int& x,int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
		,_ret(x)
		,_n(1)
		,_t(12)
		,ptr((int*)malloc(12))
	{
		if(ptr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		else
		{
			memset(ptr, 0, 12);
		}
		//error:Time没有合适的默认构造函数可以
		//error:Date::_ref必须初始化引用
		//error:Date::_n必须初始化常量限定类型的对象
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private: //由上向下初始化
	int _year = 1;
	int _month = 1;
	int _day = 1;

	int& _ret;     //引用
	const int _n;  //const
	Time _t;	   //默认构造
	int* ptr;
};

void Test01()
{
	int i = 0;
	Date d1(i ,2025, 11, 28);
	d1.Print();
}

int main()
{
	Test01();

	return 0;
}
cpp 复制代码
class Date
{
public:
	Date(int& x,int year, int month, int day)
		,_month(12)
	{}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private: //由上向下初始化
	int _year = 1;
	int _month = 1;
	int _day;
};

输出:1/12/(随机值)

以冒号开头,用逗号分隔成员初始化项,格式为:成员名(初始值)

初始化列表的核心特点:

  • 必须使用的场景:引用成员、const 成员、没有默认构造函数的自定义类型成员,必须在初始化列表中初始化。
  • 缺省值配合:C++11 支持成员变量声明时给缺省值,未在初始化列表显式初始化的成员会使用缺省值。
  • 初始化顺序:按成员在类中的声明顺序初始化,与初始化列表的顺序无关(建议保持顺序一致,避免混淆)。
  • 效率优势:即使不显式写初始化列表,成员也会先通过初始化列表初始化,直接在列表中初始化可减少一次赋值操作。

二、类型转换

C++ 支持类与其他类型的隐式转换,通过构造函数实现,但需注意边界和安全性。

核心规则:

  1. 内置类型转类类型:若类有单个参数的构造函数(或除第一个参数外其余有缺省值),可隐式转换。
cpp 复制代码
    class A {
    public:
        A(int a = 0) : _a(a) {} // 支持int转A
    private:
        int _a;
    };
    
    int main() {
        A a = 10; // 隐式转换:10 -> A(10)
        return 0;
    }
  1. 禁止隐式转换 :在构造函数前加explicit关键字,避免意外转换。
cpp 复制代码
    class A {
    public:
        explicit A(int a) : _a(a) {} // 禁止int隐式转A
    private:
        int _a;
    };
    
    int main() {
        // A a = 10; // 编译错误:禁止隐式转换
        A a(10); // 正确:显式构造
        return 0;
    }
  1. 多参数转换 :C++11 支持用初始化列表{}进行多参数隐式转换。
cpp 复制代码
    class Point {
    public:
        Point(int x, int y) : _x(x), _y(y) {}
    private:
        int _x, _y;
    };
    
    void func(Point p) {}
    
    int main() {
        func({1, 2}); // 隐式转换:{1,2} -> Point(1,2)
        return 0;
    }

三、static 成员

static修饰的成员属于类本身,而非某个对象,所有对象共享该成员。

1、静态成员变量:

  • 必须在类外初始化(类内仅声明)。
  • 存储在静态区,不计入对象大小。
cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
	A()
	{}

private:
	static int _scount; //声明
};

int A::_scount = 0;//类外初始化

int main()
{
	cout << sizeof(A) << endl; //输出:1(空类大小为1,静态成员不计入)
	return 0;
}

2、静态成员函数:

  • this指针,只能访问静态成员。
  • 可通过类名::函数名对象.函数名调用。
cpp 复制代码
class A
{
public:
	static int M()
	{
		//静态成员函数不能访问非静态
		//_a;
	}

	void Print()
	{
		//非静态成员函数可以访问静态
		cout << A::GetACount() << endl;
		cout << _scount << endl;
	}
private:
	static int _scount;
	int _a;	
}
cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
	A()
	{
		_scount++;
	}

	A(const A& a)
	{
		_scount++;
	}

	~A()
	{
		_scount--;
	}

	static int GetACount()
	{
		return _scount;
	}

private:
	static int _scount;
};

//类外初始化
int A::_scount = 0;

int main()
{
	//cout << A::_scount << endl;
	//cout << sizeof(A) << endl; //1

	cout << A::GetACount() << endl;     //0
	A a1, a2;
	cout << A::GetACount() << endl;		//2
	A a3;
	cout << A::GetACount() << endl;		//3

	{	//局部域,出去则销毁
		A a4;
		cout << A::GetACount() << endl;	//4
	}

	cout << A::GetACount() << endl;		//3
	cout << a3.GetACount() << endl;		//3
	
	return 0;
}

3、应用场景:

  • 统计类的对象个数(如示例中的_count)。
  • 实现单例模式(全局唯一实例)。

四、友元

友元提供了突破类封装的方式,允许外部函数或类访问私有成员,但会增加耦合度,应谨慎使用。

1、友元函数

外部函数声明前加friend,放入类中(任意访问限定符下均可)。

cpp 复制代码
class A {
    friend void PrintA(const A& a); // 友元声明
private:
    int _a = 10;
};

void PrintA(const A& a) {
    cout << a._a << endl; // 可访问私有成员
}

int main() {
    A a;
    PrintA(a); // 输出:10
    return 0;
}

2、友元类

若类 B 是类 A 的友元,则 B 的所有成员函数均可访问 A 的私有成员(单向关系,不可传递)。

cpp 复制代码
class A {
    friend class B; // B是A的友元
private:
    int _a = 10;
};

class B {
public:
    void PrintA(const A& a) {
        cout << a._a << endl; // 可访问A的私有成员
    }
};

int main() {
    A a;
    B b;
    b.PrintA(a); // 输出:10
    return 0;
}

五、内部类

一个类定义在另一个类内部,称为内部类。内部类是外部类的友元,但外部类不是内部类的友元。

1、特点:

  • 内部类可直接访问外部类的私有成员。
  • 内部类的实例独立于外部类对象,需通过外部类名::内部类名定义。
cpp 复制代码
class Outer {
public:
    class Inner { // 内部类
    public:
        void Print(const Outer& o) {
            cout << o._x << endl; // 可访问外部类私有成员
        }
    };
private:
    int _x = 20;
};

int main() {
    Outer o;
    Outer::Inner in; // 定义内部类对象
    in.Print(o); // 输出:20
    return 0;
}

2、应用场景:

当两个类关联紧密(如ListNode),内部类可隐藏实现细节,提高封装性

六、匿名对象

类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的类型 对象名(实参)定义出来的叫有名对象

匿名对象生命周期只在当前一行,一般临时定义一个对象当前用以下即可,就可以定义匿名对象

cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
   A(int n)
   	:a((int*)malloc(sizeof(int)* n))
   	,size(0)
   	,capacity(n)
   {
   	if (a == nullptr)
   	{
   		perror("malloc fail");
   		return;
   	}
   }

   ~A()
   {
   	cout << "~A()" << endl;
   }

private:
   int* a;
   int size;
   int capacity;
};

int main()
{
   // 有变量名(有名对象)
   A a(4);

   //无变量名(匿名对象)
   A(4);
   
   return 0;
}

七、对象拷贝时的编译器优化

现代编译器会在不影响结果的前提下,省略不必要的拷贝构造,提高效率。

常见优化场景:

  1. 构造 + 拷贝构造 --> 直接构造
cpp 复制代码
    class A {
    public:
        A(int a = 0) { cout << "A(int)" << endl; }
        A(const A&) { cout << "A(const A&)" << endl; }
    };
    
    int main() {
        A a = 10; // 优化为:直接调用A(10)
        return 0;
    }
    ```
2. **传参 / 返回值优化**
```cpp
    A func() {
        return A(10); // 优化为:直接构造返回对象
    }
    
    int main() {
        A a = func(); // 优化为:直接构造a
        return 0;
    }
cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a = 0)" << endl;
	}
	
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}
	
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		
		if(this != &aa)
		{
			_a1 = aa._a1;
		}
		
		return *this;
	}
	
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;	
};

int main()
{
	//拷贝构造+构造 -> 直接构造
	A aa1 = 1;
	
	//aa2为引用 + 构造
	const A& aa2 = 1;
	
	return 0;
}
cpp 复制代码
//没有传值返回
//(A aa)没有&(引用),走拷贝构造
void func1(A aa)
{}

int main()
{
	//构造+拷贝构造
	A aa1;
	func1(aa1);
	
	//隐式类型,连续构造+拷贝构造 -> 直接构造
	func1(1);
	
	//一个表达式(匿名函数)中,连续构造+拷贝构造 -> 一个构造
	func1(A(2));
	return 0;
}
cpp 复制代码
//传返回值
A func2()
{
	A aa;
	return aa;
}


int main()
{
	//不优化的情况下传值返回,编译器会生成一个拷贝返回对象的临时对象作为函数调用表达式的返回值
	//优化的情况下,将构造的局部对象和拷贝构造的临时对象优化为直接构造
	func2();
	
	//vs2019:返回一个表达式中,连续拷贝构造+拷贝构造 -> 一个拷贝构造
	//vs2022:优化的情况下,进行跨行合并优化,将构造的局部对象aa和拷贝的临时对象和接收返回值对象aa2优化为一个直接构造
	A aa2 = func2();
	
	//vs2019:返回一个表达式中,开始构造,中间拷贝构造+赋值重载
	//vs2022:跨行合并优化,将构造的局部对象aa和拷贝临时对象合并为一个直接构造
	aa1 = func2();
	
	return 0;
}

八、补充:类的六大默认成员函数

若类中未显式定义,编译器会自动生成以下 6 个默认成员函数:

  1. 构造函数:初始化对象。
  2. 析构函数:释放资源(如内存、文件句柄)。
  3. 拷贝构造函数:用已有对象初始化新对象。
  4. 赋值运算符重载:对象间赋值。
  5. 取地址运算符重载 :返回对象地址(&)。
  6. const 取地址运算符重载 :返回 const 对象地址(const &)。

示例:析构函数

cpp 复制代码
class Array {
public:
    Array(int size) : _size(size) {
        _data = new int[size];
    }
    ~Array() { // 手动释放内存
        delete[] _data;
        _data = nullptr;
    }
private:
    int* _data;
    int _size;
};
相关推荐
Han.miracle2 小时前
优选算法-005 有效三角形的个数(medium)
数据结构·算法·有效的三角形个数
huohuopro2 小时前
结构体与链表
数据结构·算法·链表
CoovallyAIHub2 小时前
告别“消失的小目标”:航拍图像检测新框架,精度飙升25.7%的秘诀
深度学习·算法·计算机视觉
第二只羽毛2 小时前
外卖订餐管理系统
java·大数据·开发语言·算法
发疯幼稚鬼2 小时前
希尔排序与堆排序
c语言·数据结构·算法·排序算法
小尧嵌入式2 小时前
Linux的shell命令
linux·运维·服务器·数据库·c++·windows·算法
Jeremy爱编码2 小时前
leetcode热题路径总和 III
算法·leetcode·职场和发展
hd51cc2 小时前
MFC消息处理机制
c++·mfc
CoovallyAIHub2 小时前
滑雪季又来了!如何用计算机视觉帮雪场解决最头疼的问题
深度学习·算法·计算机视觉