第十章:对象和类 | C++ Primer Plus 重点带看

类实例的成员共享与独立

类的所有实例共享同一份成员函数代码,但各自拥有独立的数据成员(注意:静态数据成员是共享的,只有一份)。成员函数通过隐式 this 指针区分调用对象。静态成员(数据和函数)被所有实例共享。

class Person {
private:
    string name;
    int age;
public:
    void setInfo(string n, int a) {
        name = n;
        age = a;
    }
    void display() {
        cout << name << ": " << age << endl;
    }
};
// 编译器实现:
// void Person::display(Person* this) {
//     cout << this->name << ": " << this->age << endl;
// }

Person p1, p2, p3;  // 三个对象

// 各自独立的数据
p1.setInfo("Alice", 20);
p2.setInfo("Bob", 25);
p3.setInfo("Carol", 30);

// 共享同一份成员函数代码
p1.display();  // Alice: 20,会传入 this = &p1,编译器转换为:Person::display(&p1);
p2.display();  // Bob: 25,会传入 this = &p2,编译器转换为:Person::display(&p2);
p3.display();  // Carol: 30,会传入 this = &p3,编译器转换为:Person::display(&p3);

┌────────────────────────────────────────────┐
│ 成员函数(代码段,只有一份)                 │
│                                            │
│   void setInfo(string n, int a) { ... }    │
│   void display() { ... }                   │
│                                            │
└────────────────────────────────────────────┘
                    ▲
        ┌───────────┼───────────┐
        │           │           │
        ▼           ▼           ▼
   ┌─────────┐ ┌─────────┐ ┌─────────┐
   │ p1      │ │ p2      │ │ p3      │
   │ name    │ │ name    │ │ name    │
   │ age     │ │ age     │ │ age     │
   └─────────┘ └─────────┘ └─────────┘
      栈/堆       栈/堆      栈/堆

   各自独立的    各自独立的    各自独立的
   数据成员      数据成员      数据成员

构造函数特征

构造函数没有返回类型(连 void 都不是),名字与类名相同,可重载,可有参数和默认参数。构造函数不能是 const、static 或 virtual,因为构造时对象类型尚未完全确定,需要修改对象状态。

class Person {
private:
    string name;
public:
    // 名字与类名相同
    Person() : name("default") { }              // 默认构造函数
    Person(string n) : name(n) { }              // 重载
    Person(const Person& other) : name(other.name) { }  // 拷贝构造
    Person(Person&& other) noexcept : name(std::move(other.name)) { }  // 移动构造
    
    // ✗ 不能是 const(构造函数需要修改对象)
    // Person() const { }
    
    // ✗ 不能是 static(构造函数需要 this 指针)
    // static Person() { }
    
    // ✗ 不能是 virtual(构造时对象类型尚未完全确定,虚函数需要对象存在(需要 vptr),但对象还没构造好)
    // virtual Person() { }
};

默认构造函数

仅当没有定义任何构造函数时,编译器才提供默认构造函数。

自定义默认构造函数有两种方式:无参构造函数全默认参数构造函数两者不能同时存在,否则调用时产生二义性

可用 = default 显式要求编译器提供。

析构函数

析构函数无参数、无返回类型、不可重载。

多态场景下基类析构函数必须是 virtual,否则派生类析构函数不会被调用,导致资源泄漏。

析构顺序与构造顺序相反。除非使用定位 new,否则不要显式调用析构函数

类私有成员访问的方式

1、类的成员函数:任何成员函数(公有、私有、保护)都可以访问私有成员。

class Person {
private:
    string name;
    
public:
    string getName() { return name; }      // 公有成员函数访问
    void setName(string n) { name = n; }
    
private:
    void secretMethod() { cout << name; }  // 私有成员函数也可访问
};

2、友元函数

class Person {
private:
    string name;
    friend void showSecret(const Person& p);  // 友元声明
};

void showSecret(const Person& p) {
    cout << p.name << endl;  // 可访问私有成员
}

3、友元类

class Person {
private:
    string name;
    friend class Spy;  // Spy 的所有成员函数都可访问
};

class Spy {
public:
    void steal(const Person& p) {
        cout << p.name << endl;  // 可访问私有成员
    }
};

注意:友元声明可放在类定义的任意位置(public/private/protected),效果相同。因为友元不是类成员,不受访问控制符限制。通常习惯放在类定义的开头或结尾,以示区分。

类的默认访问控制

class 的默认成员访问继承访问私有。(struct 的默认访问类型是公有,这是类和结构唯一的区别,只是人们习惯把结构限制为只表示纯粹数据的对象;类习惯用于封装数据和行为的对象)

this 指针

this 指针指向调用成员函数的对象,是非 const 指针(className* const,const 成员函数中为 const 指针:const ClassName* const)。

// 源代码
void Person::setName(string name) {
    this->name = name;
}

// 编译器视角
void Person_setName(Person* this, string name) {
    this->name = name;
}

// 调用转换
p1.setName("Alice");  // Person_setName(&p1, "Alice");

用途包括:区分同名成员与参数、链式调用(返回 *this)、自赋值检测、传递当前对象指针。

class Person {
private:
    string name;
public:
    // 1. 区分成员与参数同名
    void setName(string name) {
        this->name = name;
    }
    
    // 2. 返回当前对象引用(链式调用)
    Person& setName(string name) {
        this->name = name;
        return *this;
    }
    
    // 3. 自赋值检测
    Person& operator=(const Person& other) {
        if (this == &other) return *this;
        name = other.name;
        return *this;
    }
    
    // 4. 传递当前对象
    void registerSelf() {
        someManager.registerPerson(this);
    }
};

this 不能被修改,为空时调用成员函数是未定义行为。

Person* p = nullptr;
p->setName("Alice");  // 未定义行为!

// 如果 setName 不访问任何成员,可能"碰巧"不崩溃
// 但仍然是未定义行为,绝不能依赖

const 成员函数

1、const 成员函数不能修改对象成员(mutable 除外),其 this 指针为 const 类型。

class Person {
public:
    void display() const;
    void setAge(int a);
};

// 编译器视角:
// void display(const Person* this);    // this 是 const 指针
// void setAge(Person* this);           // this 是非 const 指针

2、const 对象只能调用 const 成员函数。const 成员函数可与非 const 版本构成重载。

class Person {
private:
    string name;
    int age;
public:
    void display() const {  // const 成员函数
        cout << name << ": " << age << endl;  // ✓ 可读取
        // name = "new";    // ✗ 不能修改成员
        // age = 0;         // ✗ 不能修改成员
    }
    
    void setAge(int a) {
        age = a;  // ✓ 非const函数可以修改
    }
};
Person p1;
p1.display();  // ✓ 正常对象可调用 const 函数
p1.setAge(20); // ✓ 正常对象可调用非 const 函数

const Person p2("Alice", 25);
p2.display();   // ✓ const 对象可调用 const 函数
// p2.setAge(30);  // ✗ const 对象不能调用非 const 函数

3、mutable 关键字允许在 const 函数中修改特定成员。

class Counter {
private:
    mutable int count;  // mutable 成员
    string name;
public:
    void display() const {
        count++;         // ✓ mutable 成员可修改
        // name = "x";   // ✗ 非 mutable 成员不可修改
    }
};

inline 关键字的现代含义

现代 C++ 中,inline 主要是链接指令,允许多个翻译单元定义同一函数/变量,链接器合并为同一实体。

内联展开的优化决策由编译器独立决定,inline 关键字只是建议,编译器可忽略。没有 inline 的简短函数也可能被自动内联展开。

// 有 inline,不一定展开
inline void hugeFunction() {
    // 1000行代码...
    // 编译器:太长了,不展开
}

// 没有 inline,也可能展开
void tinyFunction() {
    return x + 1;
    // 编译器:简单,自动内联展开
}

类内定义成员函数与内联

类内定义的成员函数隐式内联,但 inline 只是建议,编译器决定是否真正展开。简短函数通常被内联展开,复杂函数可能只生成函数调用。

内联可消除调用开销,但过度内联会导致代码膨胀。类外定义需显式加 inline(注意:inline 定义要放在头文件,否则放对应 cpp 里可能导不出符号,导致链接错误)。

inline 函数中的 static 变量

C++11 起标准明确要求,inline 函数中的静态局部变量在整个程序中只有一个实例,所有翻译单元共享。这与 static 函数不同——后者每个翻译单元有独立的函数副本和静态变量

// header.h
inline void func() {
    static int count = 0;
    cout << ++count << endl;
}

// a.cpp
#include "header.h"
void testA() { func(); }

// b.cpp
#include "header.h"
void testB() { func(); }

// main.cpp
#include "header.h"
int main() {
    func();    // 输出 1
    testA();   // 输出 2
    testB();   // 输出 3
}

函数类型

函数实例数

静态局部变量实例数

inline 函数

1(合并)

1(共享)

static 函数

每个翻译单元1个

每个翻译单元1个

普通函数

1

1

C++ Primer Plus 文章被收录于专栏

C++ Primer Plus 精读|从入门到面试,重点内容全程带看。 本专栏以《C++ Primer Plus》为蓝本,逐章提炼必考知识点、易错点、面试高频考点,跳过冗余示例,直击语法本质与工程实践,帮你高效吃透 C++ 基础,夯实底层开发必备能力。

全部评论
构造函数特征
点赞 回复 分享
发布于 04-02 11:17 北京
欢迎订阅专栏《C++/嵌入式开发-秋招面经》:https://www.nowcoder.com/creation/manager/columnDetail/MKaoll
点赞 回复 分享
发布于 04-01 23:17 河北

相关推荐

评论
6
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务