C++基础知识

  • 《C++ Primer》:系统学习语法和语言机制。
  • 《STL 源码剖析》:理解容器、迭代器和算法的实现。
  • 《Effective C++》:学习工程实践和常见陷阱。
  • C++23 原计划中的网络库没有如期进入标准,标准特性不等于编译器已经完整支持。

2. 命名空间 namespace

命名空间用于隔离同名变量、函数和类型,解决名字冲突。

复制代码
#include <iostream>

namespace companyA {
    int value = 10;
    void Print() { std::cout << "A\n"; }
}

namespace companyB {
    int value = 20;
}

int main() {
    std::cout << companyA::value << '\n';
    std::cout << companyB::value << '\n';

    using companyA::Print; // 只展开一个成员
    Print();
}

三种使用方式:

复制代码
std::cout << "hello";          // 推荐:明确指定
using std::cout;               // 只引入常用成员
using namespace std;           // 全部展开,大型项目不推荐

同名命名空间可以分散在多个文件中,编译器会把它们视为同一个命名空间。命名空间只能定义在全局或另一个命名空间中,不能定义在函数内部。

3. C++输入输出

std::cin 是标准输入对象,std::cout 是标准输出对象。

复制代码
#include <iostream>
#include <string>

int main() {
    std::string name;
    int age;

    std::cin >> name >> age;
    std::cout << name << " is " << age << " years old\n";
}

<< 是流插入运算符,>> 是流提取运算符。std::endl 不仅换行,还会刷新缓冲区;普通换行一般使用 '\n',效率更高。

4. 缺省参数

缺省参数允许调用函数时省略部分实参。

复制代码
void Print(int a, int b = 10, int c = 20);

int main() {
    Print(1);        // 1, 10, 20
    Print(1, 2);     // 1, 2, 20
    Print(1, 2, 3);
}

void Print(int a, int b, int c) {
    std::cout << a << ' ' << b << ' ' << c << '\n';
}

规则:

  • 缺省参数必须从右向左连续出现。

  • 传参必须从左向右,不能跳过中间参数。

  • 声明和定义分离时,通常只在声明中写缺省值。

  • 同一个参数不能重复指定缺省值。

    void Func(int a, int b = 2, int c = 3); // 正确
    // void Func(int a = 1, int b, int c = 3); // 错误

5. 函数重载

同一作用域中可以存在同名函数,但参数列表必须不同。

复制代码
int Add(int a, int b) {
    return a + b;
}

double Add(double a, double b) {
    return a + b;
}

int Add(int a, int b, int c) {
    return a + b + c;
}

只有返回类型不同不能构成重载:

复制代码
int Func(int);
// double Func(int); // 错误:参数完全相同

缺省参数和重载一起使用时要避免调用歧义:

复制代码
void Show(int);
void Show(int, int = 0);

// Show(1); // 错误:两个函数都能匹配

6. 引用的本质与特性

引用是已有对象的别名,不是一个独立对象。

复制代码
int a = 10;
int& ra = a;

ra = 20;
std::cout << a; // 20

引用具有三个重要特性:

  • 定义时必须初始化。

  • 一个变量可以有多个引用。

  • 引用绑定后不能改为引用另一个对象。

    int a = 10;
    int b = 20;
    int& r = a;

    r = b; // 不是让 r 改绑 b,而是把 b 的值赋给 a

引用传参可以避免复制,也可以直接修改实参:

复制代码
void Swap(int& x, int& y) {
    int temp = x;
    x = y;
    y = temp;
}

指针需要判空和解引用,引用的语法更直接:

复制代码
void AddOneByPointer(int* p) {
    if (p != nullptr)
        ++*p;
}

void AddOneByReference(int& value) {
    ++value;
}

7. 传值返回与引用返回

传值返回会产生一个独立结果,不能作为可修改的左值。

复制代码
int TopValue(const int* data, int size) {
    return data[size - 1]; // 返回副本
}

// TopValue(data, 3) = 100; // 错误

引用返回返回原对象的别名,因此可以修改原对象:

复制代码
int& Top(int* data, int size) {
    return data[size - 1];
}

int main() {
    int data[] = {1, 2, 3};
    Top(data, 3) = 100;
    std::cout << data[2]; // 100
}

引用返回的前提是:被引用对象在函数结束后仍然存在。绝对不能返回局部变量的引用:

复制代码
int& Wrong() {
    int local = 10;
    return local; // 错误:函数结束后 local 已被销毁
}

图片中的栈顶例子可以写成两个版本:

复制代码
int STTop(const ST& stack) {
    return stack.a[stack.top - 1]; // 只读,返回副本
}

int& STTop(ST& stack) {
    return stack.a[stack.top - 1]; // 可修改,返回引用
}

这样非 const 栈可以修改栈顶,const 栈只能读取。

8. const 引用与"权限"

const int& 表示不能通过这个引用修改对象:

复制代码
int value = 20;
const int& ref = value;

// ref = 30; // 错误
value = 30;   // 正确,value 本身不是 const

所谓"权限只能缩小,不能放大":

复制代码
int a = 10;
const int& r1 = a; // 正确:可读写对象以只读方式访问

const int b = 20;
// int& r2 = b;    // 错误:试图获得修改 const 对象的权限

普通复制不受这个限制,因为复制后是新对象:

复制代码
const int a = 10;
int b = a; // 正确,b 是独立副本
b = 20;

const 引用还能绑定临时对象、表达式结果和类型转换结果:

复制代码
const int& r1 = 30;      // 绑定临时 int
const int& r2 = 10 + 20; // 绑定表达式产生的临时对象

double d = 12.34;
const int& r3 = d;       // 先生成值为 12 的临时 int,再绑定

这里 r3 引用的不是 d 本身。因此后续修改 dr3 不会跟着变化。绑定到局部 const 引用后,临时对象的生命周期会延长到该引用的作用域结束。

函数只读取参数时,优先使用 const T&

复制代码
void PrintName(const std::string& name) {
    std::cout << name << '\n';
}

它既避免复制,又能接收普通对象、const 对象和临时对象。

9. 指针与引用

引用在语言层面是别名,但编译器底层通常会用类似地址的方式实现。不能简单地说"引用就是指针"。

特性 引用 指针
初始化 必须初始化 可以不初始化
空值 正常引用不能为空 可以是 nullptr
改变目标 不能重新绑定 可以改变指向
访问对象 直接使用名称 需要 *->
常见用途 传参、返回值、别名 动态结构、可选对象、遍历

链表头指针需要被函数修改时,可以使用指针引用:

复制代码
struct Node {
    int value;
    Node* next;
};

void PushFront(Node*& head, int value) {
    Node* node = new Node{value, head};
    head = node;
}

Node*& 表示"指针变量的引用",因此函数可以修改调用者的头指针。

10. inlinenullptr 与类

inline 建议编译器把较短函数展开,减少调用开销,但编译器可以忽略这个建议。类内部定义的成员函数会被隐式视为 inline

复制代码
inline int Square(int x) {
    return x * x;
}

它适合短小、频繁调用的函数,不适合递归函数或很长的函数。现在的主要价值还包括允许函数定义安全地放在头文件中。

C++11 应使用 nullptr 表示空指针:

复制代码
void Func(int);
void Func(int*);

Func(nullptr); // 明确调用 Func(int*)

相比之下,NULL 可能被定义成整数 0,容易导致重载选择错误。

类把数据和操作数据的方法封装在一起:

复制代码
class Stack {
public:
    void Push(int value) {
        data[top++] = value;
    }

    int& Top() {
        return data[top - 1];
    }

    const int& Top() const {
        return data[top - 1];
    }

private:
    int data[100]{};
    int top = 0;
};

访问限定符含义:

  • public:类外可以直接访问。
  • private:只能由类自身及友元访问。
  • protected:类外不能直接访问,主要供派生类使用。
  • class 默认访问权限是 privatestruct 默认是 public

最需要记住的一条主线是:引用是别名;普通引用可以修改原对象,const 引用只能读取;返回引用前必须确认被引用对象在函数结束后仍然存活。

相关推荐
geovindu1 小时前
go: Iterative Algorithms
开发语言·后端·算法·golang·迭代算法
学逆向的1 小时前
汇编——JCC指令
开发语言·汇编·网络安全
mayaairi2 小时前
JS循环语句深度解析:嵌套for、while与do...while
开发语言·前端·javascript
kite01213 小时前
Go语言Map深度解析与最佳实践
开发语言·后端·golang
hold?fish:palm4 小时前
kv存储主从复制的设计与实现
c++·redis·后端
l156469484 小时前
突围!图文混合知识库难以解析,Kimi-K3 原生视觉架构深耕知识工作,DMXAPI 统一接口,缩短项目开发周期
java·大数据·开发语言
啦啦啦啦啦zzzz5 小时前
算法:贪心算法
c++·算法·leetcode·贪心算法
charlie1145141915 小时前
现代C++工程实践 WeakPtr 实战(三):WeakPtrFactory 与「最后成员」惯用法
开发语言·c++·开源项目·现代c++
IT小盘6 小时前
04-大模型流式输出原理-SSE与Python实现
开发语言·网络·人工智能·python
不如语冰6 小时前
AI大模型入门-Python进阶-上下文管理与with语句
开发语言·数据结构·数据库·人工智能·pytorch·redis·python