C++基础:Stanford CS106L学习笔记 2 初始化与引用

目录

      • [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
相关推荐
量子炒饭大师37 分钟前
David自习刷题室——【蓝桥杯刷题备战】乘法表
c语言·c++·git·职场和发展·蓝桥杯·github·visual studio
pq113_639 分钟前
开源软件学习笔记 - little_flash + littlefs
笔记·学习·spi nand flash·ft4222h·little_flash
天赐学c语言44 分钟前
12.2 - LRU缓存 && C语言内存布局
c++·算法·lru·内存布局
报错小能手44 分钟前
C++流类库 概述及流的格式化输入/输出控制
开发语言·c++
不羁的木木44 分钟前
【开源鸿蒙跨平台开发学习笔记】Day07:React Native 开发 HarmonyOS-GitCode口袋工具开发-3
学习·开源·harmonyos
yoyo君~1 小时前
深入理解PX4飞控系统:多线程并发、原子操作与单例模式完全指南
学习·单例模式·机器人·无人机
山土成旧客1 小时前
【Python学习打卡-Day17】从二分类到多分类:ROC曲线、三大平均指标与风控利器MCC/KS
python·学习·分类
2301_789015621 小时前
C++:list(带头双向链表)增删查改模拟实现
c语言·开发语言·c++·list
暗然而日章1 小时前
C++基础:Stanford CS106L学习笔记 5 内存与指针
c++·笔记·学习