C++(类与对象)(下)

👋 欢迎阅读

🎯 欢迎来到「C++ 类和对象(进阶)」深入讲解! 本文将带你从"构造函数体的局限性"出发,逐步深入理解 初始化列表、static 成员、友元、内部类 ,并最终再次审视类与对象的本质,让你对 C++ 的面向对象设计有更全局的认知。

在开始之前,建议你先:

  • 了解背景知识 :在掌握了类的默认成员函数(构造、析构、拷贝等)之后,我们还需要了解一些进阶特性,它们能帮助我们更灵活地控制对象的初始化、共享数据、突破封装限制、以及设计更清晰的类结构。

  • 明确学习目标 :掌握以下五个核心知识点------

    初始化列表 :掌握初始化列表的语法形式(在构造函数体前以冒号开头,用逗号分隔),理解它必须用于 const 成员和引用成员 的初始化,并区分"初始化"与"赋值"的本质差异;

    static 成员 :理解静态成员变量属于类而不属于对象 ,需在类外单独定义并初始化;掌握静态成员函数的特性(无 this 指针,只能访问静态成员),以及它的典型应用场景(如统计对象个数、实现单例模式);

    友元(friend) :了解友元函数和友元类的声明方式,理解它突破了类的封装性 ,能够访问类的私有成员,但同时也要明确它破坏了封装 ,应谨慎使用;

    内部类(嵌套类) :掌握将一个类定义在另一个类内部的方式,理解内部类与外部类相互独立 (仅受作用域限制),它可以直接访问外部类的静态成员,但访问非静态成员需通过外部类对象;

    再次理解类与对象 :回顾类的本质------类是一种自定义类型 ,用于描述某一类实体(对象)所拥有的属性和方法;对象则是该类类型的一个具体实例 ,在内存中占有独立空间。深刻理解"类是图纸,对象是房子"的比喻,并意识到类是抽象的,对象是具体的,类是对象的蓝图,对象是类的实体化。

一.在谈构造函数

1.1 构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值

cpp 复制代码
class Date
{
public:
  Date(int year, int month, int day)
  {
      _year = year;
      _month = month;
      _day = day;
  }
private:
    int _year;
    int _month;
    int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为初始化只能初始化一次,而构造函数体 内可以多次赋值

1.2 初始化列表

初始化列表:以一个冒号开始 ,接着是一个以逗号分隔的数据成员列表 ,每个**"** 成员变量 " 后面跟一个放在括 号中的初始值或表达式。

cpp 复制代码
class Date
{
public:
	Date(int year, int month, int day)
       : _year(year)
       , _month(month)
       , _day(day)
   {}
private:
    int _year;
    int _month;
    int _day;
};

要点:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:

引用成员变量

const成员变量

自定义类型成员(且该类没有默认构造函数时)

cpp 复制代码
class A
{
public:
     A(int a)
     	:_a(a)
     {}
private:
 	int _a;
};
class B
{
public:
     B(int a, int ref)
         :_aobj(a)
         ,_ref(ref)
         ,_n(10)
     {}
private:
     A _aobj; // 没有默认构造函数
     int& _ref; // 引用
     const int _n; // const 
};

3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

  1. 成员变量 在类中声明次序 就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
cpp 复制代码
class A
{
public:
     A(int a)
         :_a1(a)
         ,_a2(_a1)
     {}

 	void Print() 
 	{
 		cout<<_a1<<" "<<_a2<<endl;//打印结果 1 随机值
 	}
private:
     int _a2;
     int _a1;
};
int main()
{
     A aa(1);
     aa.Print();
}

1.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。接收单个参数的构造函数具体表现:

  1. 构造函数只有一个参数

  2. 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值

  3. 全缺省构造函数

格式:explicit 函数

cpp 复制代码
class Date
{
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
   explicit Date(int year)
   	:_year(year)
   {}
private:
   int _year;
   int _month;
   int _day;
};
void Test()
{
	Date d1(2022);//能过
    // 用一个整形变量给日期类型对象赋值
    // 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值

    d1 = 2023;//不能过,触发隐式类型转换
    // explicit修饰构造函数,禁止了单参构造函数类型转换的作用,这个会出问题
用
}

用explici修饰构造函数,将会禁止构造函数的隐式转换的功能

二.static成员

声明为static 的类成员 称为类的静态成员 ,用static 修饰的成员变量 ,称之为静态成员变量 ;用static 修饰成员函数 ,称之为静态成员函数静态成员变量一定要在类外进行初始化

2.1 特性

  1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区

  2. 静态成员变量 必须在类外定义,定义时不添加static关键字,类中只是声明

  3. 类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员来访问

  4. 静态成员函数没有 隐藏的this 指针,不能访问任何非静态成员

  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

聪明的大家来思考这两个问题为什么可以,为什么不可以!

  1. 静态成员函数可以调用非静态成员函数吗?不可

  2. 非静态成员函数可以调用类的静态成员函数吗?可以

三. 友元

3.1 友元函数

友元提供了一种突破封装的方式,虽然有时能带来便利,但也会增加类之间的耦合度,破坏封装性,因此不宜过度使用。

友元主要分为两种:友元函数友元类

友元解决的一个重要问题是什么?

当我们尝试重载 operator<<(输出运算符)时,会发现无法将其重载为成员函数。这是因为 cout 输出流对象与隐含的 this 指针会争夺第一个参数的位置 。成员函数的第一个参数默认为隐藏的 this 指针,这意味着左操作数必须是对象本身。然而在实际使用中,cout 需要作为第一个形参才能正常使用。因此,我们需要将 operator<< 重载为全局函数,但这又会导致无法访问类的私有成员。此时,友元机制便提供了解决方案。operator>>(输入运算符)的情况与此类似。

cpp 复制代码
class Date
{
public:
    Date(int year, int month, int day)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    
    // d1 << cout; 会转换为 d1.operator<<(&d1, cout),不符合常规调用习惯
    // 因为成员函数的第一个参数一定是隐藏的 this,所以 d1 必须放在 << 的左侧
    ostream& operator<<(ostream& _cout)
    {
        _cout << _year << "-" << _month << "-" << _day << endl;
        return _cout;
    }
    
private:
    int _year;
    int _month;
    int _day;
};

友元函数 可以直接访问类的私有成员,它是一个定义在类外部的普通函数,不属于任何类。需要在类的内部进行声明,声明时需加上 friend 关键字。

格式:friend 函数

cpp 复制代码
class Date
{
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator>>(istream& _cin, Date& d);
    
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    
private:
    int _year;
    int _month;
    int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
}

istream& operator>>(istream& _cin, Date& d)
{
    _cin >> d._year;
    _cin >> d._month;
    _cin >> d._day;
    return _cin;
}

说明:

  1. 友元函数 可以访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用 const 修饰
  3. 友元函数 可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用方式与普通函数相同

3.2 友元类

友元类的所有成员函数都可以作为另一个类的友元函数,从而访问另一个类中的非公有成员。

友元关系是单向的,不具有交换性。

  • 例如,若 Time 类声明 Date 类为其友元类,则 Date 类可以直接访问 Time 类的私有成员变量,但 Time 类无法访问 Date 类的私有成员变量。

友元关系不能传递。

  • 如果 B 是 A 的友元,C 是 B 的友元,并不能推出 C 是 A 的友元。

友元关系不能继承,关于这一点我们将在继承部分详细讲解。

四.内部类

概念:

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何越界的访问权限。

注意: 外部类默认是内部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是内部类不是外部类的友元。
特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。

  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

  3. sizeof(外部类)=外部类,和内部类没有任何关系。

cpp 复制代码
class A
{
private:
     static int k;
     int h;
public:
     class B // A天生就是B的友元
     {
     public:
         void foo(const A& a)
         {
             cout << k << endl;//OK
             cout << a.h << endl;//OK
         }
     };
};
int A::k = 1;
int main()
{
    A::B b;
    b.foo(A());
    return 0;
}

五.再次理解类与对象

类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,在用该自定义类型去实例化具体的对象。大家要重点理解类与对象的关系!

🎯 闭幕

🎉 恭喜你完成了 C++ 类与对象核心知识的学习!今天我们系统梳理了 初始化列表static 成员友元内部类 以及 再次理解类与对象 这五大核心模块。

为了巩固知识并进一步拓展,建议你:

🚀 动手实践

在本地 IDE 或在线编译器中编写代码,尝试不同的测试用例,亲身体验每个知识点的用法。

💡 深入思考

  • 初始化列表 :初始化列表和函数体内赋值的 本质区别 是什么?哪些成员 必须 在初始化列表中初始化?初始化列表的初始化顺序 由什么决定,和书写顺序有关吗?

  • static 成员 :静态成员变量 为什么必须在类外单独定义 ?静态成员函数 为什么不能访问非静态成员 ?静态成员变量 能否在类内初始化(什么情况下可以)?

  • 友元 :友元函数 破坏了封装吗 ?为什么实际开发中要 谨慎使用友元 ?友元关系是 单向还是双向能否传递

  • 内部类 :内部类和外部类的关系是什么?内部类能否直接访问外部类的非静态成员 ?内部类的 访问权限 受外部类访问限定符的影响吗?

📚 延伸挑战

  • 定义一个 Date 类,用 初始化列表 初始化 const 成员和引用成员,演示初始化列表的必要性。

  • 定义一个 Student 类,使用 静态成员变量 统计学生总人数,使用 静态成员函数 获取总人数。

  • 定义一个 Complex 复数类,使用 友元函数 重载 operator<<operator>>,实现自定义输入输出。

  • 定义一个 Outer 类,在内部定义 Inner 内部类,演示 内部类 访问外部类静态成员的用法。

如果你觉得本文对你有所帮助,欢迎:

👍 点赞 / 收藏

👤 关注作者,获取更多题解

💬 留言交流你的疑问或优化思路


🔍 深入思考参考答案

  • 初始化列表 :初始化列表是 直接构造 成员变量(只调用一次构造函数),赋值是 先构造再赋值 (调用默认构造 + 赋值运算符)。const 成员、引用成员、没有默认构造函数的类成员 必须 使用初始化列表。初始化顺序由 成员声明的顺序 决定,与初始化列表的书写顺序无关

  • static 成员 :静态成员变量属于类,所有对象共享一份 ,必须在类外单独定义以分配存储空间(C++17 起 inline static 可在类内初始化)。静态成员函数 没有 this 指针,因此不能访问非静态成员。

  • 友元 :友元 破坏了封装 ,因为可以访问私有成员。实际开发中应 谨慎使用 ,仅在必要时(如运算符重载)使用。友元关系 单向 (A 是 B 的友元不代表 B 是 A 的友元),不能传递(A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元)。

  • 内部类 :内部类 独立于外部类存在 ,不依赖外部类对象。内部类 不能直接访问外部类的非静态成员 (因为没有外部类对象)。内部类的访问权限受外部类访问限定符影响(如 private Outer::Inner 外部无法直接使用)。

🛠️ 延伸挑战参考解法

1. Date 类(初始化列表的必要性)

cpp 复制代码
/*
 * 功能:演示初始化列表的必要性
 * 场景:const 成员、引用成员、无默认构造的成员必须用初始化列表
 * 重点:初始化顺序由声明顺序决定,与初始化列表书写顺序无关
 */

#include <iostream>
using namespace std;

// ----- 辅助类:没有默认构造函数 -----
class NoDefault 
{
private:
    int _val;

public:
    NoDefault(int val)   // 只有带参构造,没有默认构造
        : _val(val) 
    {
        cout << "NoDefault(int) constructed: " << _val << endl;
    }

    void Print() const 
    {
        cout << "NoDefault: " << _val << endl;
    }
};

// ----- Date 类演示初始化列表的必要性 -----
class Date 
{
private:
    // const 成员:必须在初始化列表中初始化,不能赋值
    const int _year;

    // 引用成员:必须在初始化列表中初始化,不能赋值
    int& _ref;

    // 没有默认构造函数的类成员:必须显式初始化
    NoDefault _nd;

    // 普通成员:可以在初始化列表或函数体内赋值
    int _month;
    int _day;

public:
    // 推荐写法:全部使用初始化列表
    Date(int year, int& ref, int ndVal, int month, int day) 
        : _year(year)                // const 成员必须初始化
        , _ref(ref)                  // 引用成员必须初始化
        , _nd(ndVal)                 // 无默认构造的成员必须初始化
        , _month(month)              // 普通成员推荐用初始化列表(效率更高)
        , _day(day) 
    {
        cout << "Date constructed with initialization list" << endl;
        // _year = 2024;   // 错误!const 成员不能赋值
        // _ref = ref;     // 错误!引用成员不能赋值(只能初始化)
    }

    // ----- 错误写法(会编译失败)-----
    // Date(int year, int& ref) 
    // {
    //     _year = year;   // 错误!const 成员不能赋值
    //     _ref = ref;     // 错误!引用成员不能赋值
    //     // _nd 没有默认构造,编译错误
    // }

    void Print() const 
    {
        cout << "Year: " << _year << endl;
        cout << "Ref: " << _ref << endl;
        _nd.Print();
        cout << "Month: " << _month << ", Day: " << _day << endl;
    }
};

int main() 
{
    int x = 100;
    Date d(2024, x, 50, 8, 5);
    d.Print();

    return 0;
}

2. Student 类(静态成员变量统计总人数)

cpp 复制代码
/*
 * 功能:演示静态成员变量和静态成员函数的使用
 * 场景:统计学生总人数,所有对象共享一份计数
 * 重点:静态成员变量类外初始化,静态成员函数无 this 指针
 */

#include <iostream>
#include <string>
using namespace std;

class Student 
{
private:
    string _name;
    int _age;
    int _id;

    // 静态成员变量:属于类,所有对象共享
    static int _totalCount;   // 统计学生总人数

public:
    // 构造函数:每创建一个学生,总人数加 1
    Student(const string& name, int age, int id) 
        : _name(name), _age(age), _id(id) 
    {
        _totalCount++;
        cout << "Student " << _name << " created. Total: " << _totalCount << endl;
    }

    // 析构函数:每销毁一个学生,总人数减 1
    ~Student() 
    {
        _totalCount--;
        cout << "Student " << _name << " destroyed. Total: " << _totalCount << endl;
    }

    // 静态成员函数:获取总人数
    // 特点:没有 this 指针,只能访问静态成员
    static int GetTotalCount() 
    {
        // 不能访问非静态成员
        // cout << _name;   // 编译错误!
        return _totalCount;
    }

    void PrintInfo() const 
    {
        cout << "Name: " << _name << ", Age: " << _age << ", ID: " << _id << endl;
    }
};

// 静态成员变量必须在类外初始化(分配存储空间)
int Student::_totalCount = 0;

int main() 
{
    cout << "Initial total: " << Student::GetTotalCount() << endl;   // 0

    Student s1("Alice", 20, 1001);
    Student s2("Bob", 21, 1002);
    Student s3("Charlie", 22, 1003);

    cout << "Final total: " << Student::GetTotalCount() << endl;     // 3

    return 0;
}

3. Complex 复数类(友元函数重载 << 和 >>)

cpp 复制代码
/*
 * 功能:演示友元函数的使用
 * 场景:重载 operator<< 和 operator>>,实现自定义输入输出
 * 重点:友元函数可以访问类的私有成员
 */

#include <iostream>
using namespace std;

class Complex 
{
private:
    double _real;   // 实部
    double _imag;   // 虚部

public:
    // 构造函数(带缺省参数)
    Complex(double real = 0.0, double imag = 0.0) 
        : _real(real), _imag(imag) 
    {
        cout << "Complex(" << _real << ", " << _imag << ") constructed" << endl;
    }

    // 打印函数
    void Print() const 
    {
        if (_imag >= 0)
            cout << _real << " + " << _imag << "i" << endl;
        else
            cout << _real << " - " << -_imag << "i" << endl;
    }

    // 声明友元函数:重载运算符 <<(输出)
    // 注意:友元不是成员函数,它是全局函数
    friend ostream& operator<<(ostream& os, const Complex& c);

    // 声明友元函数:重载运算符 >>(输入)
    friend istream& operator>>(istream& is, Complex& c);
};

// ----- 友元函数定义(可以访问 Complex 的私有成员) -----
ostream& operator<<(ostream& os, const Complex& c) 
{
    // 直接访问私有成员 _real 和 _imag
    if (c._imag >= 0)
        os << c._real << " + " << c._imag << "i";
    else
        os << c._real << " - " << -c._imag << "i";
    return os;   // 返回 ostream 引用,支持链式输出
}

istream& operator>>(istream& is, Complex& c) 
{
    // 直接访问私有成员 _real 和 _imag
    cout << "Enter real and imag: ";
    is >> c._real >> c._imag;
    return is;   // 返回 istream 引用,支持链式输入
}

int main() 
{
    Complex c1(3.0, 4.0);
    cout << "c1 = " << c1 << endl;   // 调用友元 operator<<

    Complex c2;
    cin >> c2;   // 调用友元 operator>>
    cout << "c2 = " << c2 << endl;

    return 0;
}

4. 内部类(Outer 和 Inner)

cpp 复制代码
/*
 * 功能:演示内部类的用法
 * 场景:Outer 类内部定义 Inner 内部类,Inner 访问 Outer 的静态成员
 * 重点:内部类不依赖外部类对象,不能直接访问外部类非静态成员
 */

#include <iostream>
#include <string>
using namespace std;

class Outer 
{
private:
    string _name;
    static int _staticData;   // 外部类的静态成员

public:
    Outer(const string& name) 
        : _name(name) 
    {}

    // ----- 内部类定义 -----
    class Inner 
    {
    private:
        int _id;

    public:
        Inner(int id = 0) 
            : _id(id) 
        {
            cout << "Inner constructed, id=" << _id << endl;
        }

        // 内部类可以访问外部类的静态成员(因为静态成员属于类,不需要对象)
        void ShowOuterStatic() const 
        {
            cout << "Outer::_staticData = " << Outer::_staticData << endl;
        }

        // 不能直接访问外部类的非静态成员(没有外部类对象)
        // void ShowOuterName() const 
        // {
        //     cout << Outer::_name << endl;   // 编译错误!
        // }

        // 想要访问外部类非静态成员,需要传入外部类对象
        void ShowOuterName(const Outer& o) const 
        {
            cout << "Outer::_name = " << o._name << endl;   // 通过对象访问
        }

        void Print() const 
        {
            cout << "Inner id: " << _id << endl;
        }
    };

    void Print() const 
    {
        cout << "Outer name: " << _name << endl;
    }

    // 访问限定符不影响内部类本身,但影响外部使用
    // 如果 Inner 是 private,外部就无法使用 Outer::Inner 类型
};

// 外部类静态成员初始化
int Outer::_staticData = 42;

int main() 
{
    // 创建外部类对象
    Outer outer("Hello");

    // 创建内部类对象(独立于外部类对象,不需要外部类实例)
    Outer::Inner inner(100);   // Inner 是 public 时才能这样使用

    inner.ShowOuterStatic();   // 访问外部类静态成员
    inner.ShowOuterName(outer); // 需要传入外部类对象才能访问非静态成员
    inner.Print();

    return 0;
}

祝你在 C++ 的学习之路上越走越稳,早日掌握更多编程技巧!下次见 🚀✨

相关推荐
张小凡vip1 小时前
Python爬虫实战:高效稳定的文件下载模块设计与实现
开发语言·爬虫·python
江屿风1 小时前
【科普】【差集&交集概念落地云相册】流食般投喂
开发语言·c++·笔记·算法·云相册
代龙涛1 小时前
WordPress header.php 与 footer.php 模板结构详解
开发语言·php·wordpress
math_hongfan1 小时前
鸿蒙AI应用性能高级评测:推理延迟/内存占用/功耗/准确率四维指标评测体系与极致调优方案
人工智能·学习·华为·harmonyos·鸿蒙
黄贵根7 小时前
JavaScript实现教培行业意向登记系统
开发语言·javascript·ecmascript
2401_868534789 小时前
RTOS之RT-Thread & Linux:线程/进程管理与调度核心差异分析
c++·python
zzzll111110 小时前
Claude Code离线安装方案揭秘
开发语言·php
wling030110 小时前
vasp虚频计算-python脚本
开发语言·windows·python·vasp计算
郝学胜-神的一滴11 小时前
Qt 高级编程 040:按钮悬浮弹出滑块弹窗的完整攻略
开发语言·c++·qt·软件工程·用户界面