auto 关键字
1. 什么是 auto?
在 C 语言中,auto 是存储类型说明符,用于声明具有自动存储期的局部变量。在函数内部定义的变量,若未声明为其他存储类型(如 static、register、extern),默认就是自动变量,所以实际中 auto 关键字常被省略。
在 C++98 和 C++03 标准中,auto 的含义与 C 语言一致,但因即使不使用 auto 声明,变量也拥有自动生命期,所以该用法多余且极少使用。
C++11 及以后标准:auto 被重新定义为自动推断变量类型的类型指示符。使用 auto 定义变量时必须进行初始化。
C++14:auto 可用于推导普通函数的返回类型。例如:auto func() { return 42; },编译器会根据 return 语句推导出函数返回类型为 int。auto 可作为泛型 Lambda 表达式的参数类型,提高代码复用性。
C++17:引入模板参数推导,允许使用 auto 指定函数模板参数类型时,编译器可根据实参推导模板参数类型。引入结构化绑定,允许使用 auto 解构数组、结构体和 tuple,方便访问复合数据类型元素。
总结:auto 在 C++ 中的应用,尤其是在编写模板代码或处理复杂类型时,能大大简化代码编写,提高编程效率。
在编译阶段,编译器会根据初始化表达式来推导 auto 实际代表的类型,此时 auto 只是一个类型声明时的'占位符'。
auto num = 10; // num 会被推导为 int 类型
auto str = std::string("hello"); // str 会被推导为 std::string 类型
2. 使用关键字 auto 时需要注意什么?
- 必须初始化:auto 必须通过初始化表达式推导类型,否则会导致编译错误。
- 推导规则可能与预期不符:
- 忽略顶层 const 和引用。
- 数组和函数会退化为指针。
- 声明指针或引用时的语法差异:
- 引用类型:声明引用时必须显式使用&,否则 auto 会推导为值类型(非引用)。
- 指针类型:使用 auto 声明指针时,auto 和 auto*等价(*可加可不加),因为编译器会根据初始化表达式自动推导为指针类型。
- 同一行声明多个变量时类型必须一致:当在同一行使用 auto 声明多个变量时,所有变量的类型必须完全一致,否则会编译报错。
- 不能作为函数参数,但可作为返回值(谨慎使用):
- 作为函数参数:auto 无法用于函数参数的类型声明。
- 作为函数返回值:C++14 允许 auto 作为函数返回类型(需通过 return 语句推导唯一类型),但需注意函数体必须可见。
3. 怎么使用 auto 关键字?
#include <iostream>
using namespace std;
// 可以作返回值,但建议谨慎使用(需确保返回类型明确)
auto func(){
return 3; // 返回类型被推导为 int
}
int main(){
cout << "============== 基础类型推导 ==============" << endl;
int a = 10;
auto b = a; // b 的类型推导为 int
auto c = 'a'; // c 的类型推导为 char
auto d = func(); // d 的类型由 func1() 的返回类型决定
// 打印类型信息
cout << "b 的类型是:" << typeid(b).name() << endl;
cout << "c 的类型是:" << typeid(c).name() << endl;
cout << "d 的类型是:" << typeid(d).name() << endl;
cout << "============== 指针和引用推导 ==============" << endl;
int x = 10;
// 知识点 1:使用 auto 声明指针时,auto 和 auto*等价
auto y = &x; // y 的类型推导为 int*
auto* z = &x; // 显式指定指针类型,z 的类型为 int*
// 知识点 2:声明引用时必须显式使用&
auto& w = x; // w 的类型推导为 int&
cout << "y 的类型是:" << typeid(y).name() << endl;
cout << "z 的类型是:" << typeid(z).name() << endl;
cout << "w 的类型是:" << typeid(w).name() << endl;
cout << "============== 多变量声明限制 ==============" << endl;
auto aa = 1, bb = 2; // 合法:aa 和 bb 都被推导为 int
cout << "aa 的类型是:" << typeid(aa).name() << endl;
cout << "bb 的类型是:" << typeid(bb).name() << endl;
cout << "============== 数组类型限制 ==============" << endl;
// 错误示例:auto 不能用于声明数组类型
// auto array[] = { 4, 5, 6 };
return 0;
}
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main(){
/*------------------创建一个 map 字典------------------*/
std::map<std::string, std::string> dict ={
{"apple", "苹果"},
{"orange", "橙子"},
{"pear", "梨"}
};
/*------------------创建迭代器------------------*/
// 使用 auto 自动推导迭代器类型(现代 C++ 推荐写法)
auto it = dict.begin();
/*------------------使用迭代器遍历 map 字典------------------*/
while(it != dict.end()){
cout << it->first << ":" << it->second << endl;
++it;
}
return 0;
}
范围 for 循环
1. 什么是范围 for 循环?
范围 for 循环(Range-based for loop)是 C++11 引入的一种语法糖,用于简化遍历容器或序列的过程。传统的 for 循环需要显式指定循环范围,不仅代码冗长,还容易因索引越界等问题引入错误,而基于范围的 for 循环则提供了更简洁、易读的语法,避免了传统 for 循环中迭代器或索引的显式使用,自动完成迭代过程。从实现原理上看,范围 for 循环是迭代器模式的语法糖。
2. 怎么使用范围 for 循环?
基本语法:for (declaration : range)
- declaration:定义一个变量,用于存储每次迭代时从 range 中取出的元素。
- range:表示要遍历的范围,可以是数组、容器(如 std::vector、std::list)、初始化列表等。
工作原理:
- 对于数组,直接遍历数组的每个元素。
- 对于标准库容器(如 vector、map),使用容器的 begin() 和 end() 迭代器。
- 对于自定义类型,需提供 begin() 和 end() 成员函数或全局函数。
- 变量类型:可使用 auto 自动推导元素类型。若需修改元素值,应声明为引用类型(auto&或 const auto&)。
3. 范围 for 循环有什么优势?
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main(){
// 定义一个包含 5 个整数的数组
int array[]{1, 2, 3, 4, 5};
/******************** C++98 风格的遍历 ********************/
// 传统遍历方式特点:需要手动计算数组长度,使用下标访问元素
for(int i = 0; i < sizeof(array)/sizeof(array[0]); ++i){
array[i] *= 2;
}
for(int i = 0; i < sizeof(array)/sizeof(array[0]); ++i){
cout << array[i] << " ";
}
cout << endl;
/******************** C++11 风格的遍历 ********************/
// 范围 for 循环特点:自动推导元素类型,自动处理数组边界,代码更简洁
// 使用引用修改元素(auto&)
for(auto& e : array) e *= 2;
// 使用值访问元素(auto)
for(auto e : array) cout << e << " ";
cout << endl;
/******************** 字符串遍历示例 ********************/
string str();
( ch : str){
cout << ch << ;
}
cout << endl;
;
}
迭代器
1. 什么是迭代器?
迭代器(Iterator)是一种抽象的编程概念,用于在容器(如数组、链表、集合、映射等)中遍历元素,并访问容器中的数据,而无需暴露容器的底层实现细节。它本质上是一个'指针-like'的对象,提供了一种统一的方式来操作不同类型的容器,使得代码可以不依赖于具体容器的内部结构,从而增强代码的通用性和可移植性。
核心作用:
- 遍历容器元素:像指针一样逐一遍历容器中的元素。
- 访问容器数据:读取或修改容器中的元素。
- 统一容器操作接口:C++ 标准库中的算法都依赖迭代器来操作容器。
2. 迭代器有哪些?
2.1:按'功能强弱'进行划分
| 类型 | 功能 | 支持操作 |
|---|---|---|
| 输入迭代器 | 只能读取容器元素,单向移动 | ++it, *it, ==, != |
| 输出迭代器 | 只能写入容器元素,单向移动 | ++it, *it = value |
| 前向迭代器 | 支持读取和写入,单向移动,可多次访问 | 输入迭代器 + 可保存状态 |
| 双向迭代器 | 支持前后双向移动 | 前向迭代器 + --it |
| 随机访问迭代器 | 支持随机访问元素(类似指针算术运算) | 双向迭代器 + it+n, it[n], 比较运算符 |
2.2:按'读写权限 + 遍历方向'进行划分
| 迭代器类型 | 说明 | 典型获取方式 |
|---|---|---|
| iterator | 可读写,正向遍历容器元素的迭代器 | begin(), end() |
| const_iterator | 只读,正向遍历容器元素的迭代器 | cbegin(), cend() |
| reverse_iterator | 可读写,反向遍历容器元素的迭代器 | rbegin(), rend() |
| const_reverse_iterator | 只读,反向遍历容器元素的迭代器 | crbegin(), crend() |
3. 怎么使用迭代器?
#include <iostream>
#include <string>
using namespace std;
int main(){
string s("Hello, World!");
cout << "原始字符串:" << s << endl << endl;
/*------------------使用 iterator 正向遍历(可读写)------------------*/
cout << "iterator 正向遍历:";
for(string::iterator it = s.begin(); it != s.end(); ++it){
cout << *it;
*it = toupper(*it);
}
cout << endl << "修改后的字符串:" << s << endl << endl;
/*------------------使用 const_iterator 正向遍历(只读)------------------*/
cout << "const_iterator 正向遍历:";
for(string::const_iterator cit = s.cbegin(); cit != s.cend(); ++cit){
cout << *cit;
}
cout << endl << endl;
/*------------------使用 reverse_iterator 反向遍历(可读写)------------------*/
cout << "reverse_iterator 反向遍历:";
for(string::reverse_iterator rit = s.rbegin(); rit != s.rend(); ++rit){
cout << *rit;
*rit = tolower(*rit);
}
cout << endl << "再次修改后的字符串:" << s << endl << endl;
/*------------------使用 const_reverse_iterator 反向遍历(只读)------------------*/
cout << "const_reverse_iterator 反向遍历:";
for(string::const_reverse_iterator crit = s.crbegin(); crit != s.crend(); ++crit){
cout << *crit;
}
cout << endl;
return ;
}


