目录
-
-
- [2.1 初始化](#2.1 初始化)
-
- [2.1.1 直接初始化(C++98)](#2.1.1 直接初始化(C++98))
- [2.1.2 统一初始化(C++11)](#2.1.2 统一初始化(C++11))
- [2.1.3 结构化绑定(C++17)](#2.1.3 结构化绑定(C++17))
- [2.2 引用](#2.2 引用)
-
- [2.2.1 按值传递](#2.2.1 按值传递)
- [2.2.2 引用传递](#2.2.2 引用传递)
- [2.3 左值和右值](#2.3 左值和右值)
- [2.4 const](#2.4 const)
- [2.5 编译C++程序](#2.5 编译C++程序)
-
2.1 初始化
初始化的三种方式:
直接初始化(C++98)、统一初始化(C++11)、结构化绑定(C++17)
2.1.1 直接初始化(C++98)
语法上,直接初始化依赖于=和()
cpp
// 方式1
int main() {
int foo = 12.0; // 窄化转换
return 0;
}
// 方式2
int main() {
int foo(12.0); // 窄化转换
return 0;
}
直接初始化中,像12.0赋值给int类型的foo时,编译器不会报错。当并非有意为之时,就是一个错误,被称为"窄化转换"(narrowing conversion)。
2.1.2 统一初始化(C++11)
语法上,使用{}
优点
- 可以解决直接初始化的窄化转换问题,强制实施类型安全
- 适用于C++中所有类型
缺点
统一初始化的语法( {})可能被编译器解读为两种不同含义,进而与函数重载规则叠加时,导致歧义或不符合预期的调用结果。
cpp
struct IDCard {
std::string name;
int number;
std::string email;
};
// 统一初始化
int main() {
IDCard id { "Miguel de Cervantes", 1605, "miguel@quijote.edu" };
// 加上=也可以
IDCard id = { "AAA de Cervantes", 1605, "AAAmiguel@quijote.edu" };
return 0;
}
// 类型不匹配,报错
int main() {
IDCard id { "Miguel de Cervantes", 1605.5, "miguel@quijote.edu" };
return 0;
}
error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
IDCard id { "Miguel de Cervantes", 1605.5, "miguel@quijote.edu" };
^~~~~~
// 顺序不对,报错
int main() {
IDCard id { 1605, "miguel@quijote.edu", "Miguel de Cervantes" };
return 0;
}
不同类型统一初始化示例
cpp
// vector
#include <vector>
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
return 0;
}
// map
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> ages{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35}
};
// Accessing map elements
std::cout << "Alice's age: " << ages["Alice"] << std::endl;
std::cout << "Bob's age: " << ages.at("Bob") << std::endl;
return 0;
}
"重载冲突" 是统一初始化({} 语法)的核心缺点,本质是:统一初始化的语法特性与 C++ 函数重载规则相互作用时,可能导致编译器无法正确匹配预期的重载函数,或匹配到非预期的函数。
简要解析原因和场景:
cpp
class MyClass {
public:
MyClass(int a, int b) {} // 普通构造函数
MyClass(std::initializer_list<int> list) {} // 初始化列表构造函数
};
MyClass obj{1, 2}; // 冲突?编译器优先选择 initializer_list 版本,而非 (int,int) 版本
// 若开发者实际想调用 (int,int) 构造,语法上无法直接通过 {} 实现,需改用圆括号或调整重载
语法歧义:{} 既用于初始化,也可能触发特定重载
统一初始化使用的 {} (初始化列表),在函数调用场景中,会优先匹配参数为 std::initializer_list 的重载函数 ------ 即使存在其他 "更匹配" 的普通参数类型重载,编译器也会优先选择 initializer_list 版本,导致预期之外的匹配结果。
2.1.3 结构化绑定(C++17)
结构化绑定是一种从编译时已知大小的数据结构中初始化变量的方法。
有能从函数接受多个返回值
语法
auto [var1, var2, ..., varN] = expression;
注意,必须使用auto,让编译器推导,因为var1和var2可以为不同类型。
示例
cpp
std::tuple<std::string, std::string, std::string> getClassInfo() {
std::string className = "CS106L";
std::string location = "online";
std::string language = "C++";
return {className, location, language};
}
int main() {
auto [className, location, language] = getClassInfo();
std::cout << "Join us " << location << " for " << className << " to learn " << language << "!" << std::endl;
return 0;
}
std::tuple<Class ...>是一种数据结构,编译时大小固定。
但以下代码无法运行,因为vector的大小在编译时不固定。
cpp
std::vector<std::string> getClassInfo() {
std::string className = "CS106L";
std::string location = "online";
std::string language = "C++";
std::vector<std::string> classVector {"CS106L", "online", "C++"};
return classVector;
}
int main() {
auto [className, location, language] = getClassInfo();
std::cout << "Join us " << location << " for " << className << " to learn " << language << "!" << std::endl;
return 0;
}
2.2 引用
引用是内存中已存在事物的别名,用&表示
cpp
// 引用
int num = 5;
int& ref = num;
ref = 10; // Assigning a new value through the reference
std::cout << num << std::endl; // Output: 10

2.2.1 按值传递
默认情况下,当你向函数传递一个参数时,如果你没有将其标记为引用,函数会对该参数进行复制。传递给squareN的所有参数都是副本,因此调用函数中的N不会受到影响。这称为按值传递。
cpp
void squareN(int N) {
N*=N;
}
int main() {
int num = 5;
squareN(num);
// num is still 5 here!
return 0;
}

2.2.2 引用传递
如果我们想要将一个变量从调用函数传递到另一个函数中并对其进行修改,那么我们需要通过引用来传递它。
cpp
void squareN(int& N) {
N*=N;
}
int main() {
int num = 5;
squareN(num);
// num 25 here!
return 0;
}
由于我们使用&将N显式地声明为引用,那么调用者main()中的num将会被修改。

纠错
cpp
// 错误!
#include <iostream>
#include <math.h>
#include <vector>
void shift(std::vector<std::pair<int, int>> &nums) {
for (auto [num1, num2] : nums) {
num1++;
num2++;
}
}
// 我们不修改nums,我们修改的是nums里面的std::pair!!
// 加引用,正确!
#include <iostream>
#include <math.h>
#include <vector>
void shift(std::vector<std::pair<int, int>> &nums) {
for (auto& [num1, num2] : nums) {
num1++;
num2++;
}
}
2.3 左值和右值
右值是临时的,我们不能用引用传递右值。

cpp
// 正确
#include <iostream>
#include <math.h>
void squareN(int& n) {
n = std::pow(n, 2);
}
int main() {
int num = 5;
squareN(num); // num是左值
std::cout << num << std::endl;
return 0;
}
// 报错
#include <iostream>
#include <math.h>
void squareN(int& n) {
n = std::pow(n, 2);
}
int main() {
int num = 5;
squareN(5); // 5是右值
std::cout << num << std::endl;
return 0;
}
2.4 const
一种用于声明对象不可修改的限定符

你不能对一个常量用非常量的引用:

而是应该这样:

2.5 编译C++程序
C++是编译型语言
有一些被称为编译器的计算机程序,比如clang和g++
g++ -std=c++23 main.cpp -o main
g++:编译器命令
-std=c++23:确定了我们想编译的c++版本
main.cpp:源文件
-o:意味着你想给可执行文件起一个特定名称
main:名称
g++ -std=c++23 main.cpp
依然合法,可执行文件被命名为a.out
运行:
cpp
// 编译
g++ -std=c++23 main.cpp -o main
// 运行
./main