C++ 学习参考文档
- cppreference.com:准官方文档,同步更新(含 C++26)
介绍 C++11 核心新特性,包括统一列表初始化方式、std::initializer_list 用法、左值与右值的区别及右值引用语法。重点讲解移动语义(移动构造与赋值)如何优化资源管理,以及完美转发机制。此外涵盖可变参数模板、Lambda 表达式语法与捕获列表、Function 包装器与 Bind 绑定器等实用工具,并对比新旧标准在 STL 容器及类默认成员函数上的变化。

C++98 中一般数组和结构体可以用 {} 进行初始化。
{} 初始化{} 初始化,{} 初始化也叫做列表初始化。{} 初始化的过程中,可以省略掉 =。{} 初始化会很方便。struct Point { int _x; int _y; };
class Date {
public:
Date(int year = 1, int month = 1, int day = 1) :_year(year), _month(month), _day(day) { cout << "Date(int year, int month, int day)" << endl; }
Date(const Date& d) :_year(d._year), _month(d._month), _day(d._day) { cout << "Date(const Date& d)" << endl; }
private:
int _year; int _month; int _day;
};
Date func() {
return {};
}
int main() {
// C++98
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = {0};
Point p = { 1, 2 };
Date d1(2025, 11, 15);
Date d2 = 2025;
Date d5 = { 2025, 11, 15 };
Date d6{ 2025, 11, 15 };
Date d7{};
Insert(2025);
Insert({ 2025, 11, 15 });
int i = 0;
int j = { 1 };
int k{ 2 };
int m{};
return 0;
}
int main() {
vector<int> v1 = { 1, 2, 3, 4 };
vector<int> v2{ 1, 2, 3, 4, 5, 6, 6, 7, 7 };
map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };
v1 = { 10, 20, 30 };
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
std::initializer_list<int> mylist;
mylist = { 10, 20, 30 };
cout << sizeof(mylist) << endl;
// 这里 begin 和 end 返回的值 initializer_list 对象中存的两个指针
// 这两个指针的值跟 i 的地址更接近,说明数组存在栈上
int i = 0;
cout << mylist.begin() << endl;
cout << mylist.end() << endl;
cout << &i << endl;
return 0;
}
C++98 的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,C++11 之后我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
int main() {
// 左值:可以取地址
// 以下的 p, b, c, *p, s, s[0] 就是常见的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("11111111111111");
s[0] = 'x';
cout << &p << endl;
cout << &b << endl;
cout << &c << endl;
cout << &(*p) << endl;
cout << &s << endl;
cout << (void*)&s[0] << endl;
// 右值:不能取地址
double x = 1.1, y = 2.2;
// 以下几个 10、x + y、fmin(x, y)、string("1111") 都是常见的右值
10;
x + y;
fmin(x, y);
string("1111");
// 编译报错
//cout << &10 << endl;
//cout << &(x + y) << endl;
//cout << &(fmin(x + y)) << endl;
//cout << &string("1111") << endl;
return 0;
}
Type& r1 = x; Type&& rr1 = y; 第一个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同样的道理,右值引用就是给右值取别名。template <class T> typename remove_reference<T>::type&& move(T&& arg); 是库里面的一个函数模板,本质内部是进行强制类型转换,当然他还涉及一些引用折叠的知识。int main() {
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("11111111111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 左值引用给左值取别名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
string& r4 = s;
char& r5 = s[0];
// 右值引用给右值取别名
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
string&& rr4 = string("1111");
// 左值引用不能直接引用右值,但是 const 左值引用可以引用右值
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = fmin(x, y);
const string& rx4 = string("11111");
// 右值引用不能直接引用左值,但是右值引用可以引用 move(左值)
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
string&& rrx5 = (string&&)s;
int&& rrrx1 = move(rrx1); // rrx1 本身是左值
return 0;
}
右值引用可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。
class A {
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
int main() {
A aa1;
// const 延长匿名对象的生命周期
const A& ref1 = A();
A&& ref2 = A();
cout << "main end" << endl;
return 0;
}
原来的匿名对象在当前行初始化当前行销毁。
void f(int& x) { cout << "左值引用重载 f(" << x << ")\n"; }
void f(const int& x) { cout << "const 的左值引用重载 f(" << x << ")\n"; }
void f(int&& x) { cout << "右值引用重载 f(" << x << ")\n"; }
int main() {
f(10); // 右值 -> f(int&&)
int a = 20;
f(a); // 左值 -> f(int&)
const int b = 20;
f(b); // const 左值 -> f(const int&)
// 右值引用本身的属性是左值
int&& x = 1;
f(x); // x 是左值 -> f(int&)
f(move(x)); // move(x) 是右值 -> f(int&&)
return 0;
}
左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的值。左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如 addStrings 和 generate 函数,C++98 中的解决方案只能是被迫使用输出型参数解决。那么 C++11 以后这里可以使用右值引用做返回值解决吗?显然是不可能的,因为这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用返回也无法改变对象已经析构销毁的事实。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string.h>
#include<algorithm>
using namespace std;
namespace bit {
class string {
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() { return _str; }
iterator end() { return _str + _size; }
const_iterator begin() const { return _str; }
const_iterator end() const { return _str + _size; }
string(const char*) :_size(strlen(str)), _capacity(_size) {
cout << "string(char* str)-构造" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s) {
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s) {
cout << "string(const string& s) -- 拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s) { push_back(ch); }
}
// 移动构造
string(string&& s) {
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
string& operator=(const string& s) {
cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
if (this != &s) {
_str[0] = '\0'; _size = 0;
reserve(s._capacity);
for (auto ch : s) { push_back(ch); }
}
return *this;
}
// 移动赋值
string& operator=(string&& s) {
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
~string() {
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
if (_str) { strcpy(tmp, _str); delete[] _str; }
_str = tmp;
_capacity = n;
}
}
void push_back(char ch) {
if (_size >= _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch; ++_size;
_str[_size] = '\0';
}
string& operator+=(char ch) { push_back(ch); return *this; }
const char* c_str() const { return _str; }
size_t size() const { return _size; }
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
// 传值返回需要拷贝
string addStrings(string num1, string num2) {
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
int next = 0;
while (end1 >= 0 || end2 >= 0) {
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
str += ('0' + ret);
}
if (next == 1) str += '1';
reverse(str.begin(), str.end());
cout << &str << endl;
return str;
}
}
int main() {
bit::string s1("xxxxx");
// 拷贝构造
bit::string s2 = s1;
// 构造 + 移动构造,优化后直接构造
bit::string s3 = bit::string("yyyyy");
// 移动构造
bit::string s4 = move(s1);
cout << "******************************" << endl;
return 0;
}
编译器优化(RVO/NRVO)在某些情况下会将拷贝构造优化为直接构造。在 debug 模式下通常能看到两次拷贝,release 模式下可能合三为一变为直接构造。Linux 下可使用 g++ test.cpp -fno-elideconstructors 关闭构造优化来观察未优化的行为。
int& && r = i;,这样写会直接报错,通过模板或 typedef 中的类型操作可以构成引用的引用。f2 这样的函数模板中,T&& x 参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左值时就是左值引用,传递右值时就是右值引用,有些地方也把这种函数模板的参数叫做万能引用。// 由于引用折叠限定,f1 实例化以后总是一个左值引用
template<class T> void f1(T& x) {}
// 由于引用折叠限定,f2 实例化后可以是左值引用,也可以是右值引用
template<class T> void f2(T&& x) {}
int main() {
typedef int& lref;
typedef int&& rref;
int n = 0;
// 引用折叠
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&
f1<int>(n); // 实例化为 void f1(int& x)
f1<int>(0); // 报错
f1<int&>(n); // 折叠->实例化为 void f1(int& x)
f1<int&&>(n); // 折叠->实例化为 void f1(int& x)
f2<int>(n); // 报错
f2<int&>(n); // 折叠->实例化为 void f2(int& x)
f2<int&&>(n); // 报错
return 0;
}
// 万能引用
template<class T> void Function(T&& t) {
int a = 0;
T x = a;
cout << &a << endl;
cout << &x << endl << endl;
}
int main() {
Function(10); // 右值,推导出 T 为 int,实例化为 void Function(int&& t)
int a;
Function(a); // 左值,推导出 T 为 int&,引用折叠,实例化为 void Function(int& t)
Function(std::move(a)); // 右值,推导出 T 为 int,实例化为 void Function(int&& t)
const int b = 8;
Function(b); // 左值,推导出 T 为 const int&,实例化为 void Function(const int& t)
Function(std::move(b)); // 右值,推导出 T 为 const int,实例化为 void Function(const int&& t)
return 0;
}
Function(T&& t) 函数模板程序中,传左值实例化以后是左值引用的 Function 函数,传右值实例化以后是右值引用的 Function 函数。template <class T> T&& forward(typename remove_reference<T>::type& arg);template <class T> T&& forward(typename remove_reference<T>::type&& arg);// 万能引用
template<class T> void Function(T&& t) {
Fun(forward<T>(t));
}
int main() {
Function(10); // 右值
int a;
Function(a); // 左值
Function(std::move(a)); // 右值
const int b = 8;
Function(b); // const 左值
Function(std::move(b)); // const 右值
return 0;
}
// 折叠表达式 C++17
template <class ...Args> void Print(Args... args) {
((cout << args << " s"), ...);
cout << "\n";
}
int main() {
Print();
Print(1);
Print(1, string("xxxxx"));
Print(1, string("xxxxx"), 2.2);
return 0;
}
std::forward<Args>(args)...,否则编译时包扩展后右值引用变量表达式就变成了左值。int main() {
list<bit::string> lt;
// 传左值,跟 push_back 一样,走拷贝构造
bit::string s1("111111111111");
lt.emplace_back(s1);
cout << "*********************************" << endl;
// 右值,跟 push_back 一样,走移动构造
lt.emplace_back(move(s1));
cout << "*********************************" << endl;
// 直接把构造 string 参数包往下传,直接用 string 参数包构造 string
// 这里达到的效果是 push_back 做不到的
lt.push_back("111111111111");
cout << "*********************************" << endl;
lt.emplace_back("111111111111");
cout << "*********************************" << endl;
return 0;
}
struct Date {
int _y; int _m; int _d;
Date(int y = 1, int m = 1, int d = 1) :_y(y), _m(m), _d(d) {}
};
#pragma once
namespace bit {
template<class T> struct list_node {
list_node<T>* _next;
list_node<T>* _prev;
T _data;
template<class X> list_node(X&& x = X()) :_next(nullptr), _prev(nullptr), _data(forward<X>(x)) {}
template<class ...Args> list_node(Args&& ...args) : _next(nullptr), _prev(nullptr), _data(forward<Args>(args)...) {}
};
// ... (list implementation omitted for brevity, see original source for full code)
}
int main() {
list<pair<bit::string, int>> lt1;
pair<bit::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
lt1.emplace_back(move(kv));
lt1.emplace_back("苹果", 1); // 直接构造 pair
list<Date> lt;
Date d1{ 2025, 11, 18 };
lt.push_back(d1);
lt.emplace_back(2025, 11, 18); // 直接构造 Date
return 0;
}
class Person {
public:
Person(const char* name = "张三", int age = 18) :_name(name), _age(age) {}
private:
bit::string _name;
int _age;
};
int main() {
Person s1;
Person s2 = s1; // 拷贝构造
Person s3 = std::move(s1); // 移动构造
Person s4("xxx", 18);
s4 = std::move(s2); // 移动赋值
return 0;
}
成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表用这个缺省值初始化。
class Person {
public:
Person(const char* name = "张三", int age = 18) :_name(name), _age(age) {}
// C++11
Person(const Person& p) = delete;
Person(Person&& p) = default;
~Person() {}
private:
bit::string _name;
int _age;
};
在继承与多态章节介绍过,可以复习。
class Example {
public:
Example(int a, int b) :_x(a), _y(b) { cout << "目标构造函数\n"; }
Example(int a) : Example(a, 0) { cout << "委托构造函数\n"; }
int _x; int _y;
};
class Base {
public:
Base(int x, double d) :_x(x), _d(d) {}
Base(int x) :_x(x) {}
Base(double d) :_x(d) {}
protected:
int _x = 0; double _d = 0;
};
class Derived : public Base {
public:
using Base::Base; /*继承基类的所有构造函数*/
};
int main() {
Derived d1(1);
Derived d2(1.1);
Derived d3(2, 2.2);
return 0;
}
STL 容器在 C++11 中增加了许多新特性,如 emplace 系列接口、initializer_list 支持等。
int main() {
auto add1 = [](int x, int y){return x + y; };
cout << add1(1, 2) << endl;
auto func1 = [] { cout << "hello bit" << endl; return 0; };
func1();
int a = 0, b = 1;
auto swap1 = [](int& x, int& y) { int tmp = x; x = y; y = tmp; };
swap1(a, b);
cout << a << ":" << b << endl;
return 0;
}
struct Goods {
string _name; double _price; int _evaluate;
Goods(const char* str, double price, int evaluate) :_name(str), _price(price), _evaluate(evaluate) {}
};
int main() {
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) { return gl._price < gr._price; });
sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) { return gl._price > gr._price; });
return 0;
}
Lambda 表达式本质上是一个闭包,编译器会将其转换为一个仿函数类。
int x = 0;
auto func1 = []() { x++; };
class A {
public:
void func() {
int x = 0, y = 1;
auto f1 = [=] { _a1++; return x + y + _a1 + _a2; };
auto f2 = [&] { x++; _a1++; return x + y + _a1 + _a2; };
auto f3 = [x, this] { _a1++; return x + _a1 + _a2; };
}
private:
int _a1 = 0; int _a2 = 1;
};
int main() {
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b] { int ret = a + b; x++; return ret; };
auto func2 = [=] { int ret = a + b + c; return ret; };
auto func3 = [&] { a++; c++; d++; };
func3();
return 0;
}
std::function 是 C++11 引入的标准库中的一个通用多态函数包装器,定义在 <functional> 头文件中。它的主要作用是对可调用对象(callable objects)进行类型擦除和统一接口封装。
#include<functional>
int f(int a, int b) { return a + b; }
struct Functor { int operator() (int a, int b) { return a + b; } };
class Plus {
public:
static int plusi(int a, int b) { return a + b; }
double plusd(double a, double b) { return (a + b) * _n; }
Plus(int n = 10) :_n(n) {}
private: int _n; };
int main() {
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b) {return a + b; };
vector<function<int(int, int)>> v;
v.push_back(f);
v.push_back(Functor());
v.push_back([](int a, int b) {return a + b; });
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus ps;
cout << f5(&ps, 1.1, 1.1) << endl;
return 0;
}
bind 主要用来调整参数个数和参数顺序,placeholders 命名空间的 _1 _2 分别表示实参的第一个参数和第二个参数。bind 一般用于绑死一些固定参数。
#include<functional>
using placeholders::_1; using placeholders::_2;
int SubX(int a, int b, int c) { return (a - b - c) * 10; }
int main() {
auto f3 = bind(SubX, 10, _1, _2);
cout << f3(15, 5) << endl;
auto f8 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f8(1.1, 1.1) << endl;
return 0;
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online