C++类和对象(3)(初始化列表,类型转换,static成员,友元)

前言:什么是默认构造函数?

满足默认构造的唯一条件可以不用传任何参数就能创建对象 三种情况:

1.全缺省参数的构造函数

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

class Time {
public:
    // 这是一个默认构造函数(全缺省参数)
    Time(int hour = 0) // 有默认值 = 可以不传参
    { 
        _hour = hour;
    }//

    void print() {
        cout << "The time is " << _hour << endl;
    }
private:
    int _hour;
};

int main() {
    Time t1;    // 有默认值 = 可以不传参    
    t1.print();
    Time t2(10);//int hour = 10; 过程:你传的 10 覆盖了默认值 0 _hour = hour=10;
    t2.print();
}
  1. 无参构造函数
cpp 复制代码
#include <iostream>
using namespace std;

class Time {
public:
    // 这是一个默认构造函数(全缺省参数)
    Time() 
    { 
        _hour = 99;
    }//

    void print() {
        cout << "The time is " << _hour << endl;
    }
private:
    int _hour;
};

int main() {
    Time t1;    // 有默认值 = 可以不传参    
    t1.print();
    return 0;
}
  1. 编译器自动生成的默认构造函数(如果用户没写任何构造函数)但对int ,char等内置的类型不初始化,和未初始化的局部变量一样是随机值。
cpp 复制代码
#include <iostream>
using namespace std;

class Time {
public:
    // 这是一个默认构造函数(全缺省参数)
    void print() {
        cout << "The time is " << _hour << endl;
    }
private:
    int _hour;
};

int main() {
    Time t1;    // 有默认值 = 可以不传参    
    t1.print();
    return 0;
}

一.再探构造函数:初始化列表

1. 什么是初始化列表?

初始化列表是构造函数的一种特殊语法,用于在进入构造函数体之前初始化成员变量。

cpp 复制代码
class Date {
public:
    // 初始化列表:冒号后面跟着成员变量(初始值)
    Date(int year, int month, int day) 
        : _year(year)      // 用 year 初始化 _year
        , _month(month)    // 用 month 初始化 _month
        , _day(day)        // 用 day 初始化 _day
    {
        // 构造函数体,此时成员已经初始化完成
    }
private:
    int _year;
    int _month;
    int _day;
};

二. 为什么需要初始化列表?

核心原因:有些成员必须在初始化列表中初始化,不能在函数体内赋值。

1必须使用初始化列表的三种情况:

cpp 复制代码
class Time {
public:
    Time(int hour) : _hour(hour) {}  // 没有默认构造函数
private:
    int _hour;
};

class Date {
public:
    Date(int& ref, int n) 
        : _t(12)      // 1. 没有默认构造的类类型成员
        , _ref(ref)   // 2. 引用成员变量
        , _n(n)       // 3. const成员变量
    {
        // 如果上面不在初始化列表中初始化,这里会报错
    }
private:
    Time _t;      // 没有默认构造函数,必须在初始化列表初始化
    int& _ref;    // 引用,必须在初始化列表初始化
    const int _n; // const,必须在初始化列表初始化
};

2 初始化列表的执行顺序

重要:初始化顺序按成员在类中的声明顺序,而不是初始化列表中的顺序!

cpp 复制代码
class A {
public:
    A(int a) 
        : _a2(a)    // 虽然先写 _a2,但实际先初始化 _a1
        , _a1(_a2)  // 因为 _a1 在类中先声明
    {
        // _a1 会用 _a2 初始化,但此时 _a2 还未初始化!
        // 所以 _a1 的值是随机值
    }
    void Print() {
        cout << _a1 << " " << _a2 << endl;
    }
private:
    int _a1 = 2;  // 先声明
    int _a2 = 2;  // 后声明
};

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

:建议:声明顺序和初始化列表顺序保持一致,避免这种陷阱。

3 成员变量缺省值(C++11)

cpp 复制代码
class Date {
private:
    // 声明时直接给缺省值,供初始化列表使用
    int _year = 1900;
    int _month = 1;
    int _day = 1;
    const int _n = 1;      // const 也可以给缺省值
    int* _ptr = (int*)malloc(12);
};

// 如果初始化列表没有显示初始化,就使用缺省值
Date::Date(int year, int month, int day) 
    : _year(year)  // _year 用参数,_month 和 _day 用缺省值
{
}

4初始化列表总结

cpp 复制代码
每个构造函数都有初始化列表(即使没写)
     ↓
每个成员变量都要走初始化列表初始化
     ↓
┌─────────────────────────────────────┐
│ 显示在初始化列表 → 用指定的值初始化 │
├─────────────────────────────────────┤
│ 未显示在初始化列表                   │
│   ├─ 有缺省值 → 用缺省值初始化      │
│   └─ 无缺省值 → 内置类型:随机值    │
│                自定义类型:调用默认构造│
└─────────────────────────────────────┘

三.终极结论

成员变量的初始化 = 只能在初始化列表完成!

构造函数体 {} 里面 = 只能赋值,不能初始化!

这是 C++ 铁律!不能改!

1一句话讲透本质

  • 初始化(只能在初始化列表): 变量诞生的时候就给值
  • 赋值(在函数体 {}): 变量已经诞生了,再它的值

2. 流程是固定的,编译器强制按这个顺序走

cpp 复制代码
Date() 
    :  _a(10)   // 1. 先执行初始化列表【所有成员必须在这里诞生!】
{
    _a = 20;    // 2. 再执行函数体【这里只是赋值!】
}

3真正发生的事情:

  1. 进入构造函数前 → 必须先初始化所有成员 必须走初始化列表!躲不掉!
  2. 进入 {} 后 → 只能赋值,不能再 "初始化"

为什么:因为 前面提到的const 成员,引用成员,没有默认构造的自定义类型,3 种成员只能初始化,不能赋值。

二、类型转换:隐式转换与 explicit

1. 内置类型 → 类类型

cpp 复制代码
class A {
public:
    A(int a) : _a(a) {}  // 单参数构造函数
private:
    int _a;
};

int main() {
    // 隐式类型转换:1 → 临时A对象 → 拷贝构造aa
    A aa = 1;  // 等价于 A aa(1)
    
    // 编译器优化:连续构造+拷贝构造 → 直接构造
    // 所以上面代码实际只调用一次构造函数
    
    const A& ref = 1;  // 引用也可以绑定临时对象
}

3. 类类型之间的转换

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

class B {
public:
    B(const A& a) : _b(a.Get()) {}  // 可以用 A 构造 B
private:
    int _b;
};

int main() {
    A aa(10);
    B b = aa;        // A → B 隐式转换
    const B& rb = aa; // 引用也可以
}

4. explicit 关键字:禁止隐式转换

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

int main() {
    A aa = 1;  // ❌ 编译错误!explicit 禁止隐式转换
    A aa(1);   // ✅ 只能这样直接构造
}

三、static 成员:属于类的成员

1. static 成员变量

cpp 复制代码
class A {
public:
    A() { ++_count; }      // 每构造一个对象,计数+1
    ~A() { --_count; }     // 每析构一个对象,计数-1
    
    static int GetCount() { return _count; }  // 静态成员函数
    
private:
    static int _count;  // 静态成员变量,类内声明
};

// 类外定义和初始化(必须)
int A::_count = 0;

int main() {
    cout << A::GetCount() << endl;  // 0
    A a1, a2;
    A a3(a1);
    cout << A::GetCount() << endl;  // 3
    cout << a1.GetCount() << endl;  // 3(对象也可以调用)
}

2. static 成员的特点

特点 说明
存储位置 静态区,不属于任何对象
生命周期 程序开始到结束
访问方式 类名::成员对象.成员
初始化 必须在类外定义和初始化
缺省值 ❌ 不能在声明位置给缺省值

3. static 成员函数

cpp 复制代码
class A {
public:
    static void Func() {
        // 静态成员函数没有 this 指针
        _count = 10;      // ✅ 可以访问静态成员
        // _a = 10;       // ❌ 不能访问非静态成员
    }
private:
    int _a;
    static int _count;
};

4. 经典应用:计算1+2+...+n(不能使用乘除法、循环)

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

class Solution {
private:
    class Sum {
    public:
        Sum() {
            _ret += _i;
            _i++;
        }
    };

    static int _i;
    static int _ret;

public:
    int Sum_Solution(int n) {
        _i = 1;
        _ret = 0;

        // 修复:用new创建数组,解决"变量不能定义数组长度"报错
        Sum* arr = new Sum[n];
        delete[] arr; // 释放内存

        return _ret;
    }
};

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

// 测试
int main() {
    Solution sol;
    cout << sol.Sum_Solution(3) << endl;   // 6
    cout << sol.Sum_Solution(5) << endl;   // 15
    cout << sol.Sum_Solution(10) << endl;  // 55
    return 0;
}

算法原理不变:创建 n 个对象 → 触发 n 次构造 → 自动累加 1~n

四、友元:突破封装

1. 友元函数

cpp 复制代码
class A {
    // 声明友元函数,可以访问私有成员
    friend void Print(const A& a);
private:
    int _data = 10;
};

// 友元函数不是成员函数,可以访问私有成员
void Print(const A& a) {
    cout << a._data << endl;  // ✅ 可以访问私有成员
}

int main() {
    A a;
    Print(a);  // 输出 10
}

2. 友元类

cpp 复制代码
class A {
    friend class B;  // B 是 A 的友元,B 可以访问 A 的私有成员
private:
    int _data = 10;
};

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

3. 友元的特性

特性 说明
单向性 A 是 B 的友元,但 B 不是 A 的友元
不可传递 A 是 B 的友元,B 是 C 的友元,但 A 不是 C 的友元
位置不限 可以在类中任意位置声明,不受访问限定符影响
破坏封装 谨慎使用,不要过度依赖

五、内部类

1. 什么是内部类?

内部类默认是外部类的友元

这是 C++ 规定:在一个类内部定义的嵌套类(内部类),默认可以直接访问外部类的所有成员(public/protected/private)!

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

class Outer {
public:
    // 内部类 Inner
    class Inner {  
    public:
        // 内部类可以直接访问外部类的 private 成员!
        void Show(const Outer& o) {
            cout << o._data << endl;  
        }
    };

private:
    int _data = 10;  // 外部类私有成员
};

int main() {
    Outer::Inner inner;  // 内部类属于外部类作用域
    Outer outer;
    inner.Show(outer);   // 输出:10
    return 0;
}

2. 内部类的特点

  • 独立存在,不包含在外部类对象中(sizeof(Outer) 不包含 Inner

  • 默认是外部类的友元

  • 受外部类访问限定符限制(放在 private 就成了专属内部类)

六、匿名对象

1. 什么是有名对象 vs 匿名对象

没有名字的临时对象 = 匿名对象

  • 生命周期:只存在当前一行代码
  • 执行完这一行,立刻自动调用析构函数销毁
  • 作用:临时用一次,用完就扔
cpp 复制代码
#include <iostream>
using namespace std;

class A {
public:
    A(int a = 0) : _a(a) {
        cout << "构造 A: " << _a << endl;
    }
    ~A() {
        cout << "析构 A: " << _a << endl;
    }
private:
    int _a;
};

class Solution {
public:
    int Sum_Solution(int n) {
        cout << "调用 Sum_Solution: " << n << endl;
        return n;
    }
};

int main() {
    cout << "--- 有名对象 ---" << endl;
    A a1;       

    cout << "--- 匿名对象 ---" << endl;
    A();        
    A(10);      

    cout << "--- 匿名对象调用方法 ---" << endl;
    Solution().Sum_Solution(5); 

    cout << "--- main 结束 ---" << endl;
    return 0;
}

最终总结(背会这 3 句)

  1. A() / A(10) = 匿名对象
  2. 生命周期只有当前一行,执行完立刻销毁
  3. 最常用:Solution().方法() 临时调用一次成员函数

七、编译器优化

1. 常见的拷贝优化

现代编译器会在不影响正确性的前提下,尽量减少不必要的拷贝。

核心结论:

  • 传值传参、传值返回 → 本来都要拷贝构造
  • 匿名对象 / 隐式转换 / 局部返回编译器会优化,消除拷贝,直接构造
  • 优化规则:
    • 无名临时对象 → 100% 优化
    • 有名局部对象返回 → 大概率优化
cpp 复制代码
#include <iostream>
using namespace std;

class A {
public:
    A(int a = 0) : _a(a) {
        cout << "构造 A: " << _a << endl;
    }
    A(const A& aa) {
        cout << "拷贝构造" << endl;
    }
private:
    int _a;
};

void f1(A aa) {}

A f2() {
    A aa;
    return aa;
}

int main() {
    cout << "==== f1(a1) ====" << endl;
    A a1;
    f1(a1);        // 构造 + 拷贝

    cout << "\n==== f1(1) ====" << endl;
    f1(1);         // 优化:直接构造

    cout << "\n==== f1(A(2)) ====" << endl;
    f1(A(2));      // 优化:直接构造

    cout << "\n==== A a2 = f2() ====" << endl;
    A a2 = f2();   // 优化:直接构造 a2
    return 0;
}

分析:

相关推荐
独断万古他化3 分钟前
【Java 实战项目】多用户网页版聊天室:消息传输模块 —— 基于 WebSocket 实现实时通信
java·spring boot·后端·websocket·ajax·mybatis
yyt36304584110 分钟前
spring单例bean线程安全问题讨论
java·spring
weixin_6495556714 分钟前
C语言程序设计第四版(何钦铭、颜晖)第十一章指针进阶之奇数值结点链表
c语言·开发语言·链表
书到用时方恨少!30 分钟前
Python os 模块使用指南:系统交互的瑞士军刀
开发语言·python
我是大猴子30 分钟前
事务失效的几种情况以及是为什么(详解)
java·开发语言
-许平安-37 分钟前
MCP项目笔记六(PluginsLoader)
c++·笔记·raii·plugin system
呜喵王阿尔萨斯40 分钟前
argc & argv
c语言·c++
Vect__1 小时前
std::bind和lambda的使用
c++
她叫我大水龙1 小时前
MSYS2的C/C++,python2,python3编译环境安装脚本
c语言·c++
武藤一雄1 小时前
C#:nameof 运算符全指南
开发语言·microsoft·c#·.net·.netcore