本文假设你已具备 C 语言基础。我们将聚焦于 C++ 相对于 C 的核心扩展——这些扩展不仅是语法增量,更是编程范式的根本转变。
一、引用(Reference):C++ 最基础也最重要的扩展
1.1 引用的本质
引用不是"指向变量的指针",而是变量的别名——它与原变量共享同一段内存,且绑定后不可更改。
int a = 10;
int& ref = a; // ref 是 a 的引用
ref = 20; // a 也变成 20
// int& ref2; // ❌ 错误:引用必须初始化
// int& ref3 = 10; // ❌ 错误:不能绑定到临时值(除非 const 引用)
| 特性 | 指针 | 引用 |
|---|---|---|
是否可为 nullptr | ✅ | ❌(必须初始化) |
| 是否可以重新绑定 | ✅ | ❌(终身绑定) |
| 是否需要解引用 | *ptr | 直接使用 |
| 内存占用 | 独立的地址变量 | 编译器实现,通常不占额外内存 |
| 函数参数传递 | 显式传地址 | 语法更自然的"传引用" |
| 是否支持多级 | int** | ❌ 不支持 int&&& |
1.2 引用作为函数参数:避免值拷贝
// 1. 值传递:发生拷贝,无法修改原值
void swap_by_value(int a, int b) {
int tmp = a;
a = b;
b = tmp; // 原变量未改变
}
// 2. 指针传递:可以修改,但语法繁琐
void swap_by_ptr(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
// 3. 引用传递:兼具效率与可读性(推荐方式)
void swap_by_ref(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
// 使用
int x = 1, y = 2;
swap_by_ref(x, y); // 简洁且高效,无需 &x
1.3 const 引用:安全地访问大对象
// 传值:拷贝代价高(string 可能涉及堆分配)
void print_string(std::string s); // ❌ 低效
// const 引用:不拷贝、不修改、支持临时值绑定
void print_string(const std::string& s); // ✅ 推荐
print_string("hello"); // const 引用可以绑定到临时值
const T& 是 C++ 中函数参数传递非基本类型的黄金准则——零拷贝开销 + 不可变性保证。
二、函数重载与默认参数
2.1 函数重载(Overloading)
C++ 允许同一作用域中存在同名函数,只要参数列表不同(类型、个数或顺序):
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
std::string add(const std::string& a, const std::string& b) { return a + b; }
// 编译器通过参数类型自动选择正确的重载版本
add(1, 2); // → int 版本
add(1.5, 2.5); // → double 版本
add("Hello", "World"); // → string 版本
名称修饰(Name Mangling):编译器将 add(int, int) 编码为类似 _Z3addii 的符号,链接器据此区分不同重载。
⚠️ 返回值类型不同不能构成重载,因为调用者可能不使用返回值。
2.2 默认参数
void log(const std::string& msg, int level = 1) {
std::cout << "[" << level << "] " << msg << std::endl;
}
log("hello"); // [1] hello
log("warning", 2); // [2] warning
规则:默认参数必须从右向左连续设置,调用时从左向右匹配。
三、命名空间(Namespace):解决名称冲突
3.1 基本用法
namespace math {
const double PI = 3.1415926;
double circle_area(double r) { return PI * r * r; }
}
namespace graphics {
const double PI = 3.14; // 不同的 PI,不冲突
void draw_circle(double r);
}
// 使用
math::circle_area(5.0); // 完整限定名
graphics::draw_circle(5.0);
// 引入单个符号
using math::circle_area;
// 引入整个命名空间(大型工程中慎用,避免污染全局命名空间)
using namespace std;
3.2 匿名命名空间与嵌套
// 匿名命名空间:等效于 static,限制在当前翻译单元
namespace {
int internal_counter = 0; // 仅当前文件可见
}
// 嵌套命名空间
namespace company::project::module {
void init(); // C++17 语法,等价于嵌套三层 namespace
}
四、const 正确性:C++ 的核心设计哲学
4.1 const 的多种位置语义
const int a = 10; // a 是常量
int const b = 20; // 等价于上面(习惯写法)
int x = 5;
const int* p1 = &x; // *p1 不可改(p1 指向常量)
int* const p2 = &x; // p2 不可改(p2 本身是常量)
const int* const p3 = &x; // 两者都不可改
4.2 const 成员函数
class Date {
int year_, month_, day_;
public:
// const 成员函数:承诺不修改对象状态
int year() const { return year_; }
// 非 const 成员函数:可能修改对象
void set_year(int y) { year_ = y; }
};
const Date d{2024, 1, 1};
d.year(); // ✅ const 对象只能调用 const 成员函数
d.set_year(2025); // ❌ 编译错误
const 正确性原则:任何不修改对象状态的成员函数都应标记为 const。这不仅是规范——它使得 const 对象可以调用这些函数,是代码安全的基石。
4.3 函数重载中的 const
class String {
public:
char& operator[](size_t i) { return data_[i]; } // 读写版本
char operator[](size_t i) const { return data_[i]; } // 只读版本
private:
char data_[100];
};
String s;
s[0] = 'A'; // 调用非 const 版本,可写
const String cs;
char c = cs[0]; // 调用 const 版本,只读
// cs[0] = 'B'; // ❌ 编译错误:const 版本返回 char(非引用)
五、I/O 流:类型安全的输入输出
5.1 与 printf/scanf 的本质区别
// C 风格:运行时类型检查,类型不匹配 → 未定义行为
printf("%d", 3.14); // ❌ 编译通过,运行时崩溃或输出乱码
// C++ 风格:编译期类型检查,类型安全
std::cout << 3.14 << std::endl; // ✅ 自动推断类型
// 甚至可以处理自定义类型
std::cout << my_date << std::endl; // 通过运算符重载实现
5.2 格式化 I/O
#include <iomanip>
double pi = 3.1415926535;
std::cout << std::fixed << std::setprecision(2) << pi; // 3.14
std::cout << std::setw(10) << std::left << "hello"; // "hello "
std::cout << std::hex << 255; // ff
5.3 文件 I/O
#include <fstream>
#include <string>
// 写入
std::ofstream out("data.txt");
out << "Hello, C++ I/O!" << std::endl;
out.close();
// 读取(行模式)
std::ifstream in("data.txt");
std::string line;
while (std::getline(in, line)) {
std::cout << line << std::endl;
}
// 不需要显式 close,RAII 自动管理
六、面向对象编程:封装、继承、多态
6.1 封装与访问控制
class BankAccount {
private: // 私有:仅类内部可访问
double balance_;
std::string account_id_;
protected: // 受保护:类内部 + 派生类可访问
void log_transaction(double amount);
public: // 公有:外部可访问
BankAccount(const std::string& id, double initial_balance);
void deposit(double amount);
bool withdraw(double amount);
double balance() const;
};
默认访问控制:class 默认 private,struct 默认 public——这是 C++ 中 class 和 struct 的唯一语义区别。
6.2 构造函数与初始化列表
class Point {
double x_, y_;
public:
// 初始化列表:比在构造函数体内赋值更高效(对于非基本类型可避免默认构造 + 拷贝赋值)
Point(double x, double y) : x_(x), y_(y) {}
// 默认构造函数
Point() = default;
// 委托构造函数(C++11)
Point(double v) : Point(v, v) {}
// explicit 防止隐式转换
explicit Point(int v) : Point(static_cast<double>(v), static_cast<double>(v)) {}
};
Point p1(1.0, 2.0); // 直接初始化
Point p2{3.0, 4.0}; // 列表初始化(C++11,更推荐)
// Point p3 = 5; // ❌ explicit 阻止隐式转换
6.3 继承与虚函数
class Shape {
public:
virtual double area() const = 0; // 纯虚函数 → 抽象类
virtual ~Shape() = default; // 虚析构函数(多态对象正确释放的关键)
};
class Circle : public Shape {
double radius_;
public:
Circle(double r) : radius_(r) {}
double area() const override { // override 关键字(C++11),显式重写
return 3.14159 * radius_ * radius_;
}
};
class Rectangle : public Shape {
double width_, height_;
public:
Rectangle(double w, double h) : width_(w), height_(h) {}
double area() const override {
return width_ * height_;
}
};
// 多态使用
std::unique_ptr<Shape> s1 = std::make_unique<Circle>(5.0);
std::unique_ptr<Shape> s2 = std::make_unique<Rectangle>(4.0, 6.0);
std::cout << s1->area(); // 78.5397(动态分派到 Circle::area)
std::cout << s2->area(); // 24(动态分派到 Rectangle::area)
虚函数表(vtable)机制:每个含虚函数的类有一个虚函数表,对象在构造时设置 vptr 指向该表。s1->area() 通过 vptr 间接调用,这就是运行时多态/动态分派的实现。
6.4 继承访问控制
class Base {
public: void pub() {}
protected: void prot() {}
private: void priv() {}
};
class PublicDerive : public Base { /* pub() → public, prot() → protected */ };
class ProtectedDerive : protected Base { /* pub() → protected, prot() → protected */ };
class PrivateDerive : private Base { /* pub() → private, prot() → private */ };
⚠️ 实践中几乎只使用
public继承。protected/private继承意味着"用…实现"而非"是…的一种",通常应优先使用组合。
七、运算符重载
7.1 基本语法
class Complex {
double real_, imag_;
public:
Complex(double r = 0, double i = 0) : real_(r), imag_(i) {}
Complex operator+(const Complex& rhs) const {
return Complex(real_ + rhs.real_, imag_ + rhs.imag_);
}
Complex& operator+=(const Complex& rhs) {
real_ += rhs.real_;
imag_ += rhs.imag_;
return *this; // 支持连等:a += b += c
}
// 友元函数:允许访问私有成员(常用于对称的二元运算符)
friend Complex operator*(double k, const Complex& c) {
return Complex(k * c.real_, k * c.imag_);
}
// 流输出运算符(必须是非成员函数或友元)
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real_ << " + " << c.imag_ << "i";
return os;
}
};
Complex a(1, 2), b(3, 4);
Complex c = a + b; // operator+(a, b)
a += b; // operator+=(a, b)
Complex d = 2.0 * a; // 友元 operator*(double, Complex)
std::cout << c; // 友元 operator<<(cout, c)
7.2 不能重载的运算符
::(作用域解析)、.*(成员指针访问)、.(成员访问)、?:(三目条件)、sizeof、typeid。
八、常见误区与最佳实践
| 误区 | 正确做法 |
|---|---|
using namespace std; 写在头文件中 | 仅在 .cpp 的局部作用域使用,头文件中必须用完整限定名 |
值传递大对象(如 std::vector) | 使用 const T& 传递 |
省略类中的 virtual ~Base() | 任何作为多态基类的类都必须有虚析构函数 |
用 malloc/free 管理 C++ 对象 | 用 new/delete,更推荐用智能指针(见 C++11 专题) |
class 和 struct 混用时不理解默认访问控制 | class 默认 private,struct 默认 public;仅此而已 |
总结
本文覆盖了 C++ 相对于 C 的核心语法扩展:
- 引用 → 比指针更安全、更自然的别名机制
- 函数重载/默认参数 → 更灵活的接口设计
- 命名空间 → 解决命名冲突,支持大型工程组织
const正确性 → 编译期不可变性保证- I/O 流 → 类型安全且可扩展的输入输出系统
- OOP 三大特性 → 封装、继承(public 为主)、多态(虚函数表)
- 运算符重载 → 让自定义类型像内置类型一样直观
这些基础知识是学习 C++11 及以后现代特性的前提。下一篇我们将进入 C++11/14 的核心特性:智能指针、移动语义、Lambda 表达式——这些都是改变 C++ 编程范式的里程碑特性。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。