C++类和对象(下)

1. 再探构造函数(初始化列表)

1.1 核心定义

  • 构造函数的初始化方式之一,以冒号:开头,后跟逗号分隔的成员变量初始化列表,格式为成员变量(初始值/表达式)
  • 本质:成员变量定义和初始化的地方,无论是否显式写初始化列表,所有成员变量都会通过初始化列表完成初始化。

1.2 关键规则

  1. 每个成员变量在初始化列表中仅能出现一次。
  2. 必须在初始化列表初始化的成员:
    • 引用成员变量(int& _ref)。
    • const 成员变量(const int _n)。
    • 无默认构造函数的自定义类型成员(如Time _tTime仅有无参构造时)。
  3. C++11 支持成员变量声明时给缺省值,该缺省值主要是给未在初始化列表显式初始化的成员使用。
  4. 初始化顺序:按成员变量在类中声明的顺序初始化,与初始化列表中出现的顺序无关(建议声明顺序与列表顺序一致,避免歧义)。
  5. 未显式在初始化列表初始化的成员逻辑:
    • 类中声明有缺省值:按缺省值初始化。
    • 类中声明无缺省值:
      • 内置类型:是否初始化取决于编译器(可能为随机值)。
      • 自定义类型:调用其默认构造函数,无默认构造则编译报错。

1.3 示例代码

cpp 复制代码
#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 = 1, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        , _day(day)
        , _t(12) // 无默认构造的自定义类型
        , _ref(x) // 引用成员
        , _n(1) // const成员
    {}

    void Print() const
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    Time _t;
    int& _ref;
    const int _n;
};

int main()
{
    int i = 0;
    Date d1(i);
    d1.Print();
    return 0;
}

1.4 初始化顺序例题

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

class A
{
public:
    A(int a)
        :_a1(a)
        , _a2(_a1) // 按声明顺序,_a2先初始化(此时_a1未初始化,值随机)
    {}

    void Print() {
        cout << _a1 << " " << _a2 << endl; // 输出:1 随机值(因声明顺序为_a2在前)
    }
private:
    int _a2 = 2; // 声明顺序1
    int _a1 = 2; // 声明顺序2
};

int main()
{
    A aa(1);
    aa.Print();
    return 0;
}
  • 答案:D(输出 1 随机值)

2. 类型转换(隐式类型转换)

2.1 核心规则

  1. 内置类型→类类型:需类提供对应内置类型为参数的构造函数(单参数或多参数,C++11 支持多参数隐式转换)。
  2. 类类型→类类型:需目标类提供以源类为参数的构造函数。
  3. 禁用隐式转换:构造函数前加explicit关键字,仅允许显式转换 (如A aa = A(1),不允许A aa = 1)。
  4. 编译器优化:连续构造 + 拷贝构造可优化为直接构造(如A aa = 1优化为A aa(1),而非A(1)+ 拷贝构造)。

2.2 示例代码

cpp 复制代码
#include<iostream>
using namespace std;
class A 
{
public:
	//构造函数explict就不再支持隐式类型转换了
	//explict A(int a1)
	//这里的构造函数参数有相关内置类型
	A(int a1)
		:_a1(a1)
	{}
private:
	int _a1 = 1;
	int _a2 = 2;
};
 
void func(const A& aa=1)
{
 
}
 
class Stack 
{
public:
	void Push(const A& a)
	{
 
	}
 
};
 
int main()
{
	//1.内置类型之间的隐式类型转换
	int i = 1;
	double d = i;
 
	const double& ref1 = i;//中间的临时对象具有常性
 
	//2.内置类型和类类型之间的隐式转换
	//需要有相关内置类型为参数的构造函数
 
	//这样写是直接构造
	A a1(1);
	//这个才涉及到隐式类型转换
	A a2 = 1;
 
	const A& ref2 = a1;
	const A& ref3 = 1;
 
	//三种都可以,因为上面的函数参数是用过const修饰的(所以第二种也ok)
	func(a1);
	func(1);
	func();
 
	//我们再来看看别的使用场景
	Stack s1;
	//这样写有点麻烦了
	A a3(3);
	s1.Push(a3);
	//我们可以直接这样,方便了很多
	s1.Push(3);
 
	return 0;
}

3. static 成员(静态成员)

3.1 静态成员变量

核心特性
  1. 声明:类内用static修饰,类外必须初始化(格式:类型 类名::成员变量 = 初始值)。
  2. 存储:存放在静态区,不属于某个具体对象,为所有类对象共享。
  3. 访问:
    • 突破类域:类名::成员变量对象.成员变量
    • 受访问限定符限制(public/protected/private)。
  4. 注意:不能在类内声明时给缺省值(缺省值用于初始化列表,静态成员不走构造函数)。

3.2 静态成员函数

核心特性
  1. 声明:类内用static修饰,无隐含的this指针。
  2. 访问权限:
    • 可访问:静态成员变量、静态成员函数(无this指针,无法访问非静态成员)。
    • 不可访问:非静态成员变量、非静态成员函数。
  3. 非静态成员函数:可访问任意静态成员(静态变量 / 函数)。
  4. 访问方式:类名::成员函数()对象.成员函数()

3.3 应用场景(统计对象个数)

cpp 复制代码
#include<iostream>
using namespace std;
 
class A
{
public:
	//非静态的成员函数,可以访问任意静态成员变量以及非静态的成员变量
	A(int a = 0)
		:_a1(a)
		,_a2(a)
	{
		++_count;
	}
 
	A(const A& t)
	{
		++_count;
	}
 
	static int GetCount()
	{
		// _a1++; 不能访问非静态成员,因为static成员函数没有this
		return _count;
	}
private:
	int _a1 = 1;
	int _a2 = 1;
//public:
	// 在类里面声明,且这里不可以用缺省值
	static int _count;
};
 
//必须在类外面初始化
int A::_count = 0;
 
int main()
{
	//静态成员不存在对象之中
	A aa1;
	cout << sizeof(aa1) << endl;
	A* ptr = nullptr;
 
	A aa2 = 1;
	/*cout << ptr->_count<< endl;
	cout << aa1._count << endl;
	cout << A::_count << endl;*/
 
	//变成私有的话,需要使用一下函数来获取了
 
	cout << A::GetCount() << endl;
	cout << aa2.GetCount() << endl;
	cout << ptr->GetCount() << endl;
 
	return 0;
}

3.4 例题(求 1+2+...+n) (练习题: 求1+2+3+...+n_牛客题霸_牛客网)

cpp 复制代码
class Sum
{
public:
    Sum()
    {
        _ret += _i;
        ++_i;
    }

    static int GetRet()
    {
        return _ret;
    }
private:
    static int _i;
    static int _ret;
};

int Sum::_i = 1;
int Sum::_ret = 0;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum arr[n]; // 变长数组,创建n个Sum对象,触发构造累加
        return Sum::GetRet();
    }
};

4. 友元(突破封装)

4.1 友元函数

核心特性
  1. 声明:在类内任意位置(不受访问限定符限制)加friend关键字,格式:friend 返回值 函数名(参数列表)
  2. 本质:非类的成员函数,但可访问类的私有(private)和保护(protected)成员。
  3. 特性:
    • 可作为多个类的友元函数。
    • 仅为声明,需在类外定义函数体。
    • 需提前声明相关类(若参数含其他未定义类)。
示例代码
cpp 复制代码
#include<iostream>
using namespace std;

class B; // 前置声明:func参数含B,需提前声明

class A
{
    // 声明func为A的友元函数
    friend void func(const A& aa, const B& bb);
private:
    int _a1 = 1;
    int _a2 = 2;
};

class B
{
    // 声明func为B的友元函数
    friend void func(const A& aa, const B& bb);
private:
    int _b1 = 3;
    int _b2 = 4;
};

// 友元函数定义:可访问A和B的私有成员
void func(const A& aa, const B& bb)
{
    cout << aa._a1 << endl; // 访问A的私有成员
    cout << bb._b1 << endl; // 访问B的私有成员
}

int main()
{
    A aa;
    B bb;
    func(aa, bb);
    return 0;
}

4.2 友元类

核心特性
  1. 声明:在类内加friend class 类名;,格式:friend class B;(A 声明 B 为友元类)。
  2. 本质:友元类的所有成员函数,均可访问当前类的私有和保护成员。
  3. 规则:
    • 单向性:A 是 B 的友元,不代表 B 是 A 的友元。
    • 不可传递性:A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元。
    • 破坏封装:增加类间耦合度,不宜多用。
示例代码
cpp 复制代码
#include<iostream>
using namespace std;

class A
{
    // 声明B为A的友元类
    friend class B;
private:
    int _a1 = 1;
    int _a2 = 2;
};

class B
{
public:
    void func1(const A& aa)
    {
        cout << aa._a1 << endl; // 访问A的私有成员
    }

    void func2(const A& aa)
    {
        cout << aa._a2 << endl; // 访问A的私有成员
    }
private:
    int _b1 = 3;
    int _b2 = 4;
};

int main()
{
    A aa;
    B bb;
    bb.func1(aa); // 输出1
    bb.func2(aa); // 输出2
    return 0;
}

5. 内部类

5.1 核心定义

  • 一个类定义在另一个类的内部,称为内部类(嵌套类),是独立的类,仅受外部类的类域和访问限定符限制。
  • 外部类对象中不包含内部类成员(内部类不属于外部类对象)。

5.2 关键特性

  1. 友元关系:内部类默认是外部类的友元类,可访问外部类的私有 / 保护成员(包括静态成员)。
  2. 访问方式:外部类名::内部类名(如A::B b)。
  3. 封装性:若内部类声明在private/protected区,仅外部类可访问,成为外部类的专属类。
  4. 大小计算:外部类的大小与内部类无关(内部类不占外部类对象空间)。

5.3 示例代码

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

class A
{
private:
    static int _k; // 静态成员
    int _h = 1; // 非静态成员
public:
    // 内部类B,默认是A的友元
    class B
    {
    public:
        void foo(const A& a)
        {
            cout << _k << endl; // 访问A的静态私有成员(OK)
            cout << a._h << endl; // 访问A的非静态私有成员(OK,需通过A对象)
        }
    private:
        int _b1;
    };
};

int A::_k = 1; // 外部类静态成员初始化

int main()
{
    cout << sizeof(A) << endl; // 输出4(仅包含A的非静态成员_h,内部类B不占空间)
    A::B b; // 内部类对象定义
    A aa;
    b.foo(aa); // 输出1 和 1
    return 0;
}

5.4 例题(求 1+2+...+n,内部类版本)

cpp 复制代码
class Solution {
    // 内部类Sum
    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            ++_i;
        }
    };

private:
    static int _i;
    static int _ret;
public:
    int Sum_Solution(int n) {
        Sum arr[n]; // 变长数组,创建n个Sum对象触发累加
        return _ret;
    }
};

int Solution::_i = 1;
int Solution::_ret = 0;

6. 匿名对象

6.1 核心定义

  • 格式:类型(实参)(如A()A(1)),无对象名的临时对象。
  • 生命周期:仅当前行有效,行结束后自动调用析构函数。

6.2 关键特性

  1. 区别于函数声明:A()是匿名对象,A aa()是函数声明(返回 A 类型,无参数)。
  2. 适用场景:临时使用一个对象(无需复用),如Solution().Sum_Solution(10)(创建匿名 Solution 对象,调用成员函数)。

6.3 示例代码

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

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

class Solution {
public:
    int Sum_Solution(int n) {
        return n;
    }
};

int main()
{
    A aa1; // 有名对象
    A(); // 匿名对象,生命周期仅当前行(输出A(int a)后立即输出~A())
    A(1); // 带参匿名对象

    // 匿名对象调用成员函数
    Solution().Sum_Solution(10);
    return 0;
}

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

7.1 核心原则

  • 现代编译器在不影响正确性的前提下,会减少拷贝构造的冗余,合并连续的构造 + 拷贝构造为直接构造。
  • 优化场景:同一表达式中的连续拷贝操作(不同编译器优化程度不同,如 VS2022 优化更激进)。
  • 关闭优化:Linux 下用g++ test.cpp -fno-elide-constructors编译,禁用拷贝优化。

7.2 优化场景示例

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

class A
{
public:
    A(int a = 0)
        :_a1(a)
    {
        cout << "A(int a)" << 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;
};

void f1(A aa) {} // 传值传参

A f2() { A aa; return aa; } // 传值返回

int main()
{
    // 场景1:隐式类型转换(int→A)+ 传参拷贝 → 优化为直接构造
    f1(1); // 输出:A(int a) → ~A()(无拷贝构造)

    // 场景2:匿名对象+传参拷贝 → 优化为直接构造
    f1(A(2)); // 输出:A(int a) → ~A()(无拷贝构造)

    // 场景3:传值返回+对象初始化 → 优化为直接构造(vs2022)
    A aa2 = f2(); // 输出:A(int a) → ~A()(无拷贝构造)

    // 场景4:传值返回+赋值 → 部分优化(vs2022合并构造和临时对象拷贝)
    A aa1;
    aa1 = f2(); // 输出:A(int a)(f2中)→ A& operator= → ~A()(临时对象)
    return 0;
}
相关推荐
rit84324992 小时前
基于MATLAB的环境障碍模型构建与蚁群算法路径规划实现
开发语言·算法·matlab
lang201509282 小时前
Java JSR 250核心注解全解析
java·开发语言
Wpa.wk2 小时前
接口自动化测试 - 请求构造和响应断言 -Rest-assure
开发语言·python·测试工具·接口自动化
czhc11400756632 小时前
协议 25
java·开发语言·算法
春夜喜雨2 小时前
关于内存分配的优化与设计
c++·tcmalloc·malloc·jemallc
ae_zr2 小时前
QT动态编译应用后,如何快速获取依赖
开发语言·qt
gjxDaniel2 小时前
Kotlin编程语言入门与常见问题
android·开发语言·kotlin
范纹杉想快点毕业2 小时前
状态机设计与嵌入式系统开发完整指南从面向过程到面向对象,从理论到实践的全面解析
linux·服务器·数据库·c++·算法·mongodb·mfc
坚定学代码2 小时前
认识 ‘using namespace‘
c++