C++ 基础语法快速回顾:从 C 到 C++ 的范式转变

C++ 基础语法完整回顾:引用与指针的本质区别、函数重载与默认参数、命名空间与作用域、const 正确性、I/O 流、面向对象三大特性、运算符重载与友元函数。适合有 C 基础转向 C++ 的开发者快速建立现代 C++ 编程思维。

本文假设你已具备 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 默认 privatestruct 默认 public——这是 C++ 中 classstruct 的唯一语义区别。

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 不能重载的运算符

::(作用域解析)、.*(成员指针访问)、.(成员访问)、?:(三目条件)、sizeoftypeid


八、常见误区与最佳实践

误区正确做法
using namespace std; 写在头文件中仅在 .cpp 的局部作用域使用,头文件中必须用完整限定名
值传递大对象(如 std::vector使用 const T& 传递
省略类中的 virtual ~Base()任何作为多态基类的类都必须有虚析构函数
malloc/free 管理 C++ 对象new/delete,更推荐用智能指针(见 C++11 专题)
classstruct 混用时不理解默认访问控制class 默认 private,struct 默认 public;仅此而已

总结

本文覆盖了 C++ 相对于 C 的核心语法扩展:

  1. 引用 → 比指针更安全、更自然的别名机制
  2. 函数重载/默认参数 → 更灵活的接口设计
  3. 命名空间 → 解决命名冲突,支持大型工程组织
  4. const 正确性 → 编译期不可变性保证
  5. I/O 流 → 类型安全且可扩展的输入输出系统
  6. OOP 三大特性 → 封装、继承(public 为主)、多态(虚函数表)
  7. 运算符重载 → 让自定义类型像内置类型一样直观

这些基础知识是学习 C++11 及以后现代特性的前提。下一篇我们将进入 C++11/14 的核心特性:智能指针、移动语义、Lambda 表达式——这些都是改变 C++ 编程范式的里程碑特性。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「cpp」更多文章

  1. 模板元编程与编译期计算:TMP 实战指南
  2. STL 算法与迭代器:从 for_each 到并行执行策略
  3. STL 容器全解析与源码剖析