从零开始的C++学习生活 4:类和对象(下)

个人主页:Yupureki-CSDN博客

C++专栏:C++_Yupureki的博客-CSDN博客

目录

前言

[1. 再谈构造函数](#1. 再谈构造函数)

[2. 类型转换](#2. 类型转换)

[3. static成员](#3. static成员)

static成员变量:

static成员函数:

总结:

[4. 友元](#4. 友元)

友元函数:

友元类:

[5. 内部类](#5. 内部类)

[6. 匿名对象](#6. 匿名对象)

[7. 对象拷贝时的编译器优化](#7. 对象拷贝时的编译器优化)

常见优化场景:

示例:

总结


前言

如果说上一章我们学会了如何搭建一个"毛坯房",那么本章------《类和对象(下)》------我们将要学习的,就是如何对它进行精装修、建立小区公共设施、并制定灵活的访客规则。我们将深入挖掘C++为对象模型提供的更精密、更强大的控制机制。

1. 再谈构造函数

之前我们用构造函数初始化变量都是在函数体内完成的,而我们有另一种方式,那就是利用初始化列表

cpp 复制代码
class A
{
public:
	A(int a1 = 1, int a2 = 2)
		:_a1(a1)//初始化列表
		,_a2(a2)
	{
		cout << "A(int)" << endl;
	}
private:
	int _a1;
	int _a2;
}

我们会发现,初始化列表在构造函数的大括号外,再用传递的形参a1和a2的值进行初始化。

并且初始化列表还有以下特性:

• 每个成员变量在初始化列表中只能出现⼀次

引用 成员变量,const 成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始 化,否则会编译报错。

• C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的 成员使⽤的。

cpp 复制代码
class A
{
public:
	A(int a1, int a2 = 3)
		:_a1(a1)//如果函数参数(a1)没有缺省值,那么_a1初始化为声明处给的缺省值(1)
		,_a2(a2)//如果函数参数(a2)有缺省值,那么_a2初始化为a2的值
	{
		cout << "A(int)" << endl;
	}
private:
	int _a1 = 1;//C++11之后可在变量声明处给缺省值
	int _a2 = 2;
}

• 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没 有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器

• 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序⽆ 关。建议声明顺序和初始化列表顺序保持⼀致。

cpp 复制代码
class A
{
public:
	A(int a1 = 1)
		:_a1(a1)
		,_a2(_a1)//_a1还没开始初始化,为随机值。_a2先初始化为_a1的值为随机值
	{
		cout << "A(int)" << endl;
	}
private:
	int _a2;//_a2先声明 那么初始化时_a2先初始化
	int _a1;
}

初始化列表总结:

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

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

2. 类型转换

除了C语言中我们学到的的几种类型转换,在C++中还有其他几种

C++ 允许隐式类型转换 ,特别是从内置类型类类型的转换:

构造函数前面加explicit就不再支持隐式类型转换。

类类型的对象之间也可以隐式转换,需要相应的构造函数支持。

cpp 复制代码
class A {
public:
    A(int a) : _a(a) {}
private:
    int _a;
};

A a = 1; // 隐式转换:1 -> A(1)

使用 explicit 关键字可以禁止隐式转换

cpp 复制代码
explicit A(int a) 
         :_a(a) 
         {}
// A a = 1; // 错误:不能隐式转换
A a(1);     // 正确:显式构造

3. static成员

类中能包含用static修饰的成员函数或者成员变量,静态成员属于类本身,而非某个对象

static成员变量:

1.静态成员变量⼀定要在类外进行初始化。

cpp 复制代码
class A {
public:
    A() 
    { 
       //count = 1 错误,static成员变量不能在类内初始化
       count++; 
    }
private:
    static int count; // 声明
};

int A::count = 0; // 定义并初始化
  1. 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
cpp 复制代码
class B {
public:
	void Add()
	{
		b++;
	}
	void Print()
	{
		cout << b << endl;
	}
private:
	static int b;
};
int B::b = 1;

int main()
{
	B b1;
	B b2;
	b1.Print();
	b2.Print();
	b1.Add();
	b2.Print();
    return 0;
}
  1. 静态成员也是类的成员,受public、protected、private访问限定符的限制。

  2. 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员 变量不属于某个对象,不⾛构造函数初始化列表。

static成员函数:

  1. 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
cpp 复制代码
	static void Print()
	{
		cout << b2 << endl;
		cout << b1 << endl;//static成员函数不能访问非static成员变量
	}
    void print()
    {
        cout<<b1<<b2<<endl;//普通成员函数都可以访问
    }
private:
	static int b2;
	int b1 = 1;
};
int B::b2 = 2;
  1. 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量 和静态成员函数。

总结:

  • 静态成员变量必须在类外定义

  • 静态成员函数没有 this 指针,只能访问静态成员;

  • 可通过 类名::成员对象.成员 访问。

4. 友元

友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加friend,并且把友元声明放到⼀个类的里面。

友元函数:

  1. 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
cpp 复制代码
class A {
    friend void Print(const A& a);
private:
    int _a = 10;
};

void Print(const A& a) {
    cout << a._a << endl; // 可以访问私有成员
}
  1. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

  2. ⼀个函数可以是多个类的友元函数。

友元类:

  1. 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
cpp 复制代码
class B {
    friend class C; // C 是 B 的友元类
private:
    int _b = 20;
};

class C {
public:
    void ShowB(const B& b) {
        cout << b._b << endl; // 可以访问 B 的私有成员
    }
};
  1. 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。

如上图代码,C是B的友元类,能访问B的私有成员,但B不是C的友元类,不能访问C

  1. 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。

5. 内部类

如果⼀个类定义在另⼀个类的内部,这个里面的小类就叫做内部类,外面的大类叫做外部类。

内部类默认是外部类的友元类,因此可以内部类可以访问外部类的成员

cpp 复制代码
class Outer {//外部类
private:
    static int _k;
    int _h = 1;
public:
    class Inner {//内部类
    public:
        void Show(const Outer& o) {
            cout << _k << endl; // 可访问静态成员
            cout << o._h << endl; // 可访问实例成员
        }
    };
};

6. 匿名对象

匿名对象是一种临时对象生命周期 仅限于当前行

cpp 复制代码
class A {
public:
    A(int a = 0) : _a(a) {}
    ~A() { cout << "析构" << endl; }
};

A();        // 匿名对象,本行结束即析构
A(10);      // 带参匿名对象
Solution().Sum_Solution(10); // 常用场景:调用临时对象的函数

匿名对象主要是方便快捷,如果我们只想访问一个类中的成员函数,放以前我们只能专门创造一个对象然后访问。利用匿名对象我们无需创造类就可以直接访问

cpp 复制代码
class C {
public:
	C(int _c1 = 2)
		:c1(_c1)
	{ }
	void Print()
	{
		cout << c1 << endl;
	}
private:
	int c1 = 1;
};

int main()
{
	C c1 = 2;
	c1.Print();

	C(2).Print();//匿名对象可以直接访问成员函数
}

7. 对象拷贝时的编译器优化

现代编译器会对对象的构造和拷贝进行优化,减少不必要的临时对象

常见优化场景:

  • 连续构造 + 拷贝构造 → 合并为一次构造;

  • 返回值优化(RVO/NRVO):将返回的临时对象与接收对象合并。

示例:

cpp 复制代码
A f2() {
    A aa;
    return aa; // 可能被优化为直接构造在调用处
}

 int main()
 {
        // 传值传参
        // 构造+拷⻉构造
        A aa1;
        f1(aa1);


        f1(1);// 隐式类型,连续构造+拷⻉构造->优化为直接构造
   
 
        f1(A(2)); // ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
        cout << endl;
}

如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新⼀点的编 译器对于连续⼀个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。

总结

掌握类和对象的高级特性,能写出更高效、更安全的C++代码。尤其是:

  • 初始化列表是构造函数的真正起点;

  • 静态成员实现类的共享资源;

  • 友元内部类提供灵活的封装突破;

  • 匿名对象编译器优化提升运行时效率。

相关推荐
草莓熊Lotso2 小时前
揭开 C++ vector 底层面纱:从三指针模型到手写完整实现
开发语言·c++
MAR-Sky2 小时前
单片机学习中的一些简单总结
单片机·嵌入式硬件·学习
Dream_言十2 小时前
光全息|OAM-旋转双维度复用全息
神经网络·学习·dnn·论文笔记
小秋学嵌入式-不读研版2 小时前
C56-字符串拷贝函数strcpy与strnpy
c语言·开发语言·笔记
还有几根头发呀2 小时前
[特殊字符] LeetCode 143 重排链表(Reorder List)详解
数据结构·链表
hui函数2 小时前
python全栈(基础篇)——day04:后端内容(字符编码+list与tuple+条件判断+实战演示+每日一题)
开发语言·数据结构·python·全栈
知识分享小能手2 小时前
微信小程序入门学习教程,从入门到精通,WXS语法详解(10)
前端·javascript·学习·微信小程序·小程序·vue·团队开发
晨非辰5 小时前
《剑指Offer:单链表操作入门——从“头删”开始破解面试》
c语言·开发语言·数据结构·c++·笔记·算法·面试
悠哉悠哉愿意8 小时前
【ROS2学习笔记】 TF 坐标系
笔记·学习·ros2