第十章:对象和类 | 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++ 基础,夯实底层开发必备能力。

查看15道真题和解析