从零到一学习C++(基础篇) 作者:羡鱼肘子
温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。
温馨提示2:本篇会尽量避免一些术语,尽量用更加通俗的语言介绍c++的基础,但术语也是很重要的。
温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。
auto类型说明符
C++ 中的
auto
是一个类型说明符,用于让编译器自动推导变量类型。它从 C++11 开始引入,是现代 C++ 中简化代码、增强可读性的重要工具。
1. 核心作用:自动类型推断
auto
的核心逻辑是:编译器根据初始化表达式自动确定变量类型。类似于“让编译器猜类型”,但规则明确,不会出错。
基础用法示例
auto a = 42; // a 的类型是 int
auto b = 3.14; // b 的类型是 double
auto c = "Hello"; // c 的类型是 const char*
auto d = 'A'; // d 的类型是 char
2.为什么用
auto
?
-
简化复杂类型声明:尤其是模板、迭代器等冗长类型。
-
避免类型重复书写:代码更简洁。
-
支持泛型编程:处理未知类型更方便。
简化迭代器的作用
// 传统写法(类型冗长)
std::vector<int> vec = {1, 2, 3};
std::vector<int>::iterator it = vec.begin();
// 使用 auto(简洁明了)
auto it = vec.begin(); // 自动推导为 std::vector<int>::iterator
3.
auto
的推导规则简单理解:可以把auto看成“还原剂”,对引用和顶层const起反应
-
去掉引用和顶层
const
:
auto
默认推导为值类型,引用和const
需要显式声明。 -
保留底层
const
:
若初始化表达式是底层const
(如const int*
),auto
会保留。
强化理解:底层const VS 顶层const(这一部分很难但也很重要)
在这一部分我会用比较专业的用语来解释,因为我认为在这一块一味的通俗比方很可能会让人“误入歧途”
顶层const(Top-Level Const)
-
定义:直接修饰变量本身,表示该变量不可修改。
-
特点:
-
适用于所有数据类型(基本类型、指针、类对象等)。
-
对象本身是常量,任何修改操作都会导致编译错误。
-
const int a = 10; // a是顶层const,值不可修改
int *const p = &b; // p是顶层const,指针本身不可修改(指向的地址固定)
const double pi = 3.14; // pi是顶层const
应用场景
-
定义全局常量或需要保护不被修改的局部变量。
-
固定指针的指向地址(如硬件寄存器指针)。
底层const(Low-Level Const)
-
定义:修饰指针或引用所指向的对象,表示不能通过该指针或引用修改目标对象。
-
特点:
-
仅适用于指针或引用类型。
-
指向的对象本身可能是常量或非常量,但通过该指针/引用无法修改。
-
const int *p = &a; // p是底层const,不能通过p修改a的值
const int &r = a; // r是底层const,不能通过r修改a的值
const std::string *s; // s指向的字符串内容不可修改
应用场景
-
函数参数传递时,避免意外修改传入的数据。
-
处理常量数据(如字符串字面量、配置文件等)。
顶层const vs 底层const对比
特性 | 顶层const | 底层const |
---|---|---|
修饰对象 | 变量本身 | 指针/引用指向的对象 |
适用类型 | 所有类型 | 仅指针和引用 |
函数重载 | 无法区分重载 | 可以区分重载 |
参数传递 | 严格匹配类型 | 接受常量或非常量实参 |
典型示例 | int *const p | const int *p |
小结一下:
顶层const:保护变量本身不被修改,适用于所有类型。
底层const:保护指针或引用指向的内容不被修改,增强代码安全性,“只读”访问。
要合理使用哦:
函数参数优先使用底层const,提高接口通用性。
顶层const用于需要固定变量或指针地址的场景。
欧克,我们来找个例子看看
int x = 10;
const int y = 20;
int& ref = x;
const int* ptr = &y;
// auto 推导
auto a = x; // a 是 int
auto b = ref; // b 是 int
auto c = ptr; // c 是 const int*
auto d = &y; // d 是 const int*
-
去引用(Reference Stripping)
auto b = ref;
中,ref
是int&
类型,但auto
会推导为int
(值类型),而非引用。
结果:b
是独立的int
变量,值为x
的拷贝(10)。 -
保留底层
const
(Pointer to Const)
const int* ptr = &y;
中,ptr
是“指向常量整型的指针”。
auto
推导:auto c = ptr;
会保留底层const
,因此c
的类型是const int*
(正确)。auto d = &y;
中,y
是const int
,其地址类型为const int*
。结果:d
的类型是const int*
(正确)。
显式保留引用或
const
int x = 10;
const int y = 20;
auto& a = x; // a 是 int&(保留引用)
const auto& b = y; // b 是 const int&(保留引用和 const)
auto* c = &x; // c 是 int*(显式指针类型)
4. 常见使用场景
(1) 简化迭代器和范围 for 循环
std::vector<int> vec = {1, 2, 3};
// 传统迭代器写法
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) { ... }
// 使用 auto + 范围 for 循环
for (auto num : vec) { ... } // 值拷贝
for (auto& num : vec) { ... } // 引用(可修改元素)
for (const auto& num : vec) { ... } // 常量引用(只读)
(2) 泛型编程中的未知类型(目前可以跳过)
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // C++11 需要尾置返回类型
return a + b;
}
// C++14 更简洁
template<typename T, typename U>
auto add(T a, U b) {
return a + b;
}
auto result = add(3, 4.5); // result 类型为 double
(3) Lambda 表达式
auto lambda = [](int a, int b) { return a + b; };
auto sum = lambda(3, 4); // sum 类型为 int
5.
auto
的注意事项
(1) 必须初始化
auto a; // 错误!无法推导类型
(2) 推导结果可能与预期不同(目前了解一下就好)
const int x = 10;
auto y = x; // y 是 int(去掉了顶层 const)
auto& z = x; // z 是 const int&(保留 const)
int arr[] = {1, 2, 3};
auto arr_copy = arr; // arr_copy 是 int*(数组退化为指针)
auto& arr_ref = arr; // arr_ref 是 int(&)[3](保留数组类型)
(3) 函数返回类型推导(C++14+)
auto func() { // 返回类型由 return 语句推导
return 42; // 返回 int
}
auto func2() { // 返回类型需一致
if (condition) return 10; // int
else return 3.14; // double → 错误!
}
6.
auto
vsdecltype
-
auto
:根据初始化表达式推导类型。 -
decltype
:根据表达式推导类型,但不计算表达式的值。
int x = 10;
const int& y = x;
auto a = y; // a 是 int(去掉了引用和顶层 const)
decltype(y) b = x; // b 是 const int&(精确匹配表达式类型)
最终建议:合理使用
auto
,既能提高编码效率,又能减少低级错误!🚀
decltype
decltype
是 C++11 引入的关键字,用于推导表达式的类型。它的核心逻辑是:“告诉我某个表达式是什么类型,但不要实际计算它的值”。
基础用法:直接获取类型
decltype(表达式)
会返回表达式的精确类型(包括 const
、引用等修饰符)。
int x = 10;
const double y = 3.14;
int& ref = x;
decltype(x) a = x; // a 的类型是 int
decltype(y) b = y; // b 的类型是 const double
decltype(ref) c = x; // c 的类型是 int&(引用)
decltype(x + y) d = x + y; // d 的类型是 double(int + double → double)
与 auto
的关键区别
特性 | auto | decltype |
---|---|---|
推导依据 | 根据初始化表达式推导类型 | 根据传入的表达式推导类型 |
引用和 const | 默认忽略顶层 const 和引用 | 保留所有修饰符(包括引用和 const ) |
计算表达式值 | 必须初始化(依赖表达式值) | 不计算表达式的值(仅分析类型) |
对比示例
int x = 10;
const int& rx = x;
auto a = rx; // a 是 int(忽略引用和 const)
decltype(rx) b = x; // b 是 const int&(保留所有修饰符)
常见用途
(1) 定义复杂类型别名
#include <vector>
using Vec = std::vector<int>;
Vec vec = {1, 2, 3};
decltype(vec)::iterator it = vec.begin(); // 等价于 std::vector<int>::iterator
(2) 泛型编程中的返回值类型推导
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // C++11 需要尾置返回类型
return a + b;
}
auto result = add(3, 4.5); // result 类型为 double
(3) 保留引用类型(避免拷贝)
std::vector<int> vec = {1, 2, 3};
decltype(vec[0]) elem = vec[0]; // elem 是 int&(引用vec中的元素)
elem = 10; // 修改vec[0]为10
特殊规则
(1) 变量 vs 表达式
-
变量名:
decltype(var)
返回变量的声明类型(含const
和引用)。 -
表达式:
decltype(表达式)
可能推导出引用类型(如(x)
会变成int&
)。
int x = 10;
decltype(x) a = x; // a 是 int
decltype((x)) b = x; // b 是 int&(表达式 (x) 被视为左值)
(2) 结合 declval
推导类成员类型
#include <utility>
struct Point {
int x;
double y;
};
// 推导Point的成员x的类型
using XType = decltype(std::declval<Point>().x); // XType 是 int
C++14 扩展:decltype(auto)
-
用途:结合
auto
的简洁性和decltype
的精确性,常用于函数返回类型。 -
规则:返回类型由
decltype(return表达式)
决定。
注意事项
-
不计算表达式值:
decltype
只分析类型,不会执行表达式。int x = 10; decltype(x++) y = x; // y 是 int(x++ 的类型是 int,但不会真的执行x++)
-
避免冗余修饰符:
const int x = 10; decltype(x) a = 20; // a 是 const int,但初始化后不能修改
类比:
decltype
像一台“类型复印机”,原样复制表达式的类型细节。📋🔍
下一篇会学习数组,让我们一起加油。