文章目录
一、构造函数
构造函数是类的一种特殊成员函数,用于 对象创建时初始化对象的数据成员。
特点:
- 名字与类名相同。
- 没有返回值(连 void 也不写)。
- 可以被重载
- 在对象创建时自动调用。
示例:
#include <iostream>
using namespace std;
class Point {
private:
int x;
int y;
public:
// 默认构造函数
Point() {
x = 0;
y = 0;
}
// 带参数构造函数
Point(int xVal, int yVal) {
x = xVal;
y = yVal;
}
void print() {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Point p1; // 调用默认构造函数
Point p2(3, 4); // 调用带参数构造函数
p1.print(); // (0, 0)
p2.print(); // (3, 4)
}
构造函数的分类:
- 默认构造函数:无参数或所有参数都有默认值。
- 带参数构造函数:用于初始化对象。
- 拷贝构造函数:ClassName(const ClassName &other),用于用已有对象初始化新对象。
- 移动构造函数(C++11):ClassName(ClassName&& other),用于移动语义优化。
1.1 explicit 关键字
explicit 用于 修饰构造函数,阻止构造函数被 隐式类型转换 调用。
为什么需要 explicit?
在 C++ 中,如果构造函数只有一个参数,它可以被用作隐式类型转换:
class Foo {
public:
Foo(int a) { cout << "Foo(int) called" << endl; }
};
void printFoo(Foo f) { }
int main() {
printFoo(10); // 隐式转换:int -> Foo
}
这里 10 会隐式转换成 Foo(10),有时会导致不安全或难以发现的 bug。
使用 explicit 可以禁止隐式转换:
class Foo {
public:
explicit Foo(int a) { cout << "Foo(int) called" << endl; }
};
int main() {
// printFoo(10); // 错误,禁止隐式转换
printFoo(Foo(10)); // 正确,显示转换
}
总结:
- 单参数构造函数如果不加 explicit,可能会被当作类型转换。
- 建议 能加 explicit 就加,提高代码安全性。
### 1.2 构造函数初始化列表
初始化列表是一种在构造函数体执行前 直接初始化成员变量的语法:
class Point {
private:
int x;
int y;
public:
// 初始化列表写法
Point(int a, int b) : x(a), y(b) { }
void print() {
cout << "(" << x << ", " << y << ")" << endl;
}
};
为什么使用初始化列表?
- 效率更高:如果成员是对象,初始化列表直接调用构造函数,而在构造函数体里赋值会先调用默认构造再赋值。
- 常量成员、引用成员必须使用初始化列表
class Test {
const int a;
int &b;
public:
Test(int val, int &ref) : a(val), b(ref) { }
};
const 和 引用 不能在构造函数体内赋值。
- 支持基类构造函数调用:
class Base {
public:
Base(int v) { cout << "Base " << v << endl; }
};
class Derived : public Base {
public:
Derived(int x) : Base(x) { cout << "Derived " << x << endl; }
};
完整示例:
#include <iostream>
using namespace std;
class Point {
private:
const int x;
int &y;
public:
explicit Point(int a, int &bRef) : x(a), y(bRef) {
cout << "Point constructed" << endl;
}
void print() {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
int val = 20;
Point p(val, val);
p.print();
// Point p2 = 10; // 错误,explicit 禁止隐式转换
}
二、对象拷贝
2.1 概念
对象拷贝(Object Copy)是指用一个已有对象去创建或赋值另一个对象的过程。
主要分为两种情况:
拷贝构造:用一个对象来初始化另一个对象。
MyClass obj1; // 已有对象
MyClass obj2(obj1); // 调用拷贝构造函数
拷贝赋值:将一个对象赋值给另一个已经存在的对象。
MyClass obj1;
MyClass obj2;
obj2 = obj1; // 调用拷贝赋值运算符
2.2 默认的拷贝行为
如果没有显式定义拷贝构造函数和拷贝赋值运算符,C++ 会 自动生成默认版本:
- 默认拷贝构造函数:逐个成员执行浅拷贝(bitwise copy)。
- 默认拷贝赋值运算符:同样是逐个成员的浅拷贝。
这对大多数内置类型和简单类是安全的,但对于动态分配内存(指针)的类,浅拷贝可能会导致:
- 双重释放(double free)
- 悬挂指针(dangling pointer)
2.3 自定义拷贝构造函数和拷贝赋值运算符
如果类中有指针或需要特殊处理的资源,就应该手动实现。
示例:深拷贝
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
char* data;
public:
// 构造函数
MyString(const char* str = "") {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 拷贝构造函数(深拷贝)
MyString(const MyString& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
cout << "拷贝构造函数被调用" << endl;
}
// 拷贝赋值运算符(深拷贝)
MyString& operator=(const MyString& other) {
if (this == &other) return *this; // 防止自赋值
delete[] data; // 释放原内存
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
cout << "拷贝赋值运算符被调用" << endl;
return *this;
}
// 析构函数
~MyString() {
delete[] data;
}
void print() const {
cout << data << endl;
}
};
int main() {
MyString s1("Hello");
MyString s2 = s1; // 调用拷贝构造函数
MyString s3;
s3 = s1; // 调用拷贝赋值运算符
s1.print();
s2.print();
s3.print();
}
2.4 拷贝控制成员
通常与对象拷贝相关的函数有:
- 拷贝构造函数
- 拷贝赋值运算符
- 析构函数
- 移动构造函数
- 移动赋值运算符
2.5 对象拷贝过程的内存图

三、关键字 inline
3.1 什么是 inline?
在 C++ 中,inline 的主要作用是 向编译器建议:在调用函数的地方直接展开函数体,而不是生成普通函数调用。
也就是说,inline 试图消除函数调用开销(参数压栈、跳转、返回)。
但是要注意:
- 它只是“建议(hint)”,编译器可以忽略。
- 现代编译器有强大的优化器,即使你不写 inline,在优化时也可能会自动内联。
内联的适用场景:
- 短小函数(如 getter、setter、数学小函数)。
- 频繁调用的函数。
- 模板函数(通常定义在头文件里)。
不适合用 inline 的情况:
- 函数体很大(代码膨胀,反而性能下降)。
- 递归函数(编译器通常不会内联递归)。
- 虚函数(运行时多态,不能真正内联)。
底层实现原理:
调用一个普通函数时,底层一般会做:
- 参数入栈(或寄存器传递)。
- 保存返回地址。
- 跳转到函数体执行。
- 执行 ret 返回。
- 清理栈。
内联函数的编译器处理:
当函数被 inline 后,编译器在 编译阶段 可能会直接把函数体展开到调用处,例如:
源代码:
int y = add(2, 3) + add(4, 5);
编译器可能展开为:
int y = (2 + 3) + (4 + 5);
与宏的区别:
- 宏展开发生在 预处理阶段,没有类型检查,可能产生难以发现的 bug。
- inline 发生在 编译阶段,有严格的类型检查,更安全。
例如:
#define SQUARE(x) (x * x) // 宏
inline int square(int x) { return x * x; } // inline函数
调用 SQUARE(1+2) → 展开成 (1+2*1+2),结果是 5 而不是 9。而 square(1+2) 结果就是正确的 9。
现代编译器的优化:
- 编译器(如 GCC/Clang/MSVC)即使你不写 inline,在 O2/O3 优化模式下,也会自动决定是否内联。
- 相反,即使你写了 inline,如果函数太大,编译器也可能拒绝内联。
- 所以 inline 更多用于 解决多重定义问题,而不是“强制内联”。
3.2 基本用法
示例 1:普通函数内联
#include <iostream>
using namespace std;
inline int add(int a, int b) {
return a + b;
}
int main() {
cout << add(2, 3) << endl;
return 0;
}
编译器可能会把 add(2,3) 替换成:
cout << 2 + 3 << endl;
这样就避免了函数调用开销。
示例 2:类内成员函数(默认内联)
class Foo {
public:
int x;
int getX() const { return x; } // 默认inline
};
在类定义体里写的成员函数,默认就是 inline。
四、const 的基本作用
const 表示 常量 或 不可修改。在 C++ 中,它用于限制变量、指针、函数参数、成员函数等的可修改性。
主要目的:
- 提高代码安全性,防止误修改。
- 提高可读性,明确设计意图。
- 便于编译器优化。
4.1 常见用法
修饰普通变量
const int a = 10;
a = 20; // ❌ 错误,a 是只读的
修饰指针
分为三种情况:
int x = 10, y = 20;
const int* p1 = &x; // 指向的值不可改(内容只读)
int* const p2 = &x; // 指针本身不可改(地址只读)
const int* const p3 = &x; // 值和指针都不可改
- const int* p:不能通过 p 修改 *p 的值,但可以改变 p 指向谁。
- int* const p:不能改变 p 的指向,但可以修改指向对象的值。
- const int* const p:都不能改。
修饰函数参数
void foo(const int x); // 形参 x 在函数内不可修改
void bar(const int* p); // 不能通过 p 改变 *p
void baz(const int& r); // 防止引用修改,常用来避免拷贝
典型场景:传大对象时用 const &:避免拷贝,又保证只读
void print(const std::string& s) {
std::cout << s << std::endl;
}
修饰函数返回值
const int foo(); // 返回值不可被修改(通常作用有限)
const int& bar(); // 返回引用,但只读
const int* baz(); // 返回只读指针
例如:
const std::string& getName() const;
修饰类成员函数
class Test {
int value;
public:
int getValue() const { // const成员函数
return value; // ✅ 只能读
// value = 10; ❌ 错误,不能修改成员变量
}
};
const 成员函数保证 不修改类的成员变量(除 mutable 成员外)。
与 mutable 结合
mutable 成员即使在 const 函数中也能修改:
class Test {
mutable int cache;
public:
int get() const {
cache++; // ✅ 即使是 const 成员函数,也能改
return cache;
}
};
4.2 底层实现原理
- const 在 编译期生效,它主要是 编译器的语义检查,保证不被错误修改。
- 编译器会在符号表中标记变量为只读,一旦出现修改行为,直接报编译错误。
- 对于 const 全局变量,编译器可能会将其放入只读数据段(.rodata),在运行期修改会导致段错误。
- const 成员函数的底层实现:编译器会在函数签名中加一个额外的 this 指针限定:
void foo() const;
// 相当于:
void foo(const Test* const this);
所以它保证了 this 指针指向的对象不可修改。
五、this 指针
5.1 this 指针的由来
在 C 语言里,没有类(class),函数只是操作结构体数据的普通函数。例如:
struct Point { int x, y; };
void move(Point* p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
调用时必须显式传递结构体地址 move(&pt, 1, 2)。
而到了 C++,为了支持面向对象编程(把数据和操作绑定),就需要能在成员函数里隐式访问所属的对象。这时候就引入了一个隐藏的指针:this。它自动指向调用该成员函数的对象。
5.2 this 指针的基本概念
- this 是一个 隐含在所有非静态成员函数中的指针参数。
- 它指向调用该成员函数的对象本身。
- 对于类 X,在成员函数中 this 的类型是:普通成员函数:X* const this;常量成员函数:const X* const this
5.3 this 的作用
区分成员变量和局部变量
class A {
int x;
public:
void setX(int x) {
this->x = x; // 区分成员变量和参数
}
};
支持链式调用
class A {
int x;
public:
A& set(int v) { x = v; return *this; }
};
A a;
a.set(5).set(10).set(20); // 链式调用
在运算符重载中返回当前对象
class A {
int x;
public:
A(int v):x(v){}
A& operator++() {
x++;
return *this;
}
};
区分对象:
A a, b;
a.set(10); // this 指向 a
b.set(20); // this 指向 b
5.4 底层实现原理
编译器会在成员函数里偷偷加上一个隐含参数 this,本质上就是指针传参。
class A {
int x;
public:
void setX(int v) { x = v; }
};
编译器会转化为类似 C 形式:
struct A { int x; };
void setX(A* this, int v) {
this->x = v;
}
- this 并不是语法糖,它是编译器在成员函数中自动传递的对象地址。
- static 成员函数没有 this,因为它不依赖对象。
六、关键字 static
6.1 static 的作用概览
在 C++ 中,static 有不同的语义,取决于它出现的位置:
- 函数内部的局部变量:使局部变静态存储,即变量只在第一次执行时初始化,并且在整个程序运行过程中一直存在(直到程序结束)。生命周期变长,但作用域仍然局限在函数内部。
- 全局/命名空间作用域的变量或函数:限制其作用域仅在当前编译单元(.cpp 文件)内可见,避免符号冲突(内部链接)。
- 类中的成员变量(静态成员变量):属于整个类共享,而不是某个对象独有;生命周期从程序开始到结束。必须在类外进行定义(如果是非 constexpr)。
- 类中的成员函数(静态成员函数):不依赖于对象实例,可以直接通过类名调用;不能访问类的非静态成员,只能访问静态成员。
使用示例:
函数内部的 static 局部变量
#include <iostream>
using namespace std;
void counter() {
static int count = 0; // 只初始化一次
count++;
cout << "调用次数: " << count << endl;
}
int main() {
counter(); // 调用次数: 1
counter(); // 调用次数: 2
counter(); // 调用次数: 3
}
这里的 count 会在整个程序运行期间一直存在,而不是每次调用 counter() 都重新创建。
全局变量或函数的 static
// file1.cpp
static int g_value = 10; // 只在 file1.cpp 内可见
static void helper() { // 只在 file1.cpp 内可见
cout << "helper in file1.cpp" << endl;
}
// file2.cpp 无法访问 g_value 和 helper()
类的静态成员变量
#include <iostream>
using namespace std;
class MyClass {
public:
static int s_value; // 声明静态成员
};
int MyClass::s_value = 100; // 类外定义
int main() {
cout << MyClass::s_value << endl; // 100
MyClass obj1, obj2;
obj1.s_value = 200;
cout << obj2.s_value << endl; // 200,所有对象共享
}
静态成员变量存放在全局区,而不是对象实例中。
类的静态成员函数
#include <iostream>
using namespace std;
class MyClass {
public:
static void show() {
cout << "Static function called!" << endl;
}
};
int main() {
MyClass::show(); // 通过类调用
MyClass obj;
obj.show(); // 也可以通过对象调用,但不依赖对象
}
static 的总结:
- 局部变量:延长生命周期,但作用域不变。
- 全局变量/函数:限制在当前编译单元可见,避免冲突。
- 类的静态成员变量:类级别共享,需在类外定义。
- 类的静态成员函数:不依赖对象,不能访问非静态成员。

被折叠的 条评论
为什么被折叠?



