C++和标准库速成(十)——类型别名、类型定义、类型推断和标准库简介

目录

  • [1. 类型别名](#1. 类型别名)
  • [2. 类型定义(不建议)](#2. 类型定义(不建议))
  • [3. 类型推断](#3. 类型推断)
    • [3.1 auto](#3.1 auto)
      • [3.1.1 auto&](#3.1.1 auto&)
      • [3.1.2 auto*](#3.1.2 auto*)
      • [3.1.3 拷贝列表初始化和直接列表初始化](#3.1.3 拷贝列表初始化和直接列表初始化)
    • [3.2 decltype](#3.2 decltype)
  • [4. 标准库简介](#4. 标准库简介)
  • 参考

1. 类型别名

类型别名为现有的类型声明提供新名称。可以将类型别名视为用于为现有类型声明引入同义词而无须创建新类型的语法。以下为int*类型声明赋予新名称IntPtr:

using IntPtr = int*;

可以互换使用新的类型别名及其本来的名称。例如,以下两行有效。

cpp 复制代码
int* p1;
IntPtr p2;

使用新类型名称创建的变量与使用原始类型声明创建的变量完全兼容。因此,使用这些定义,编写以下内容是完全正确的,因为它们不仅是兼容的类型,还是完全相同的类型。

cpp 复制代码
p1 = p2;
p2 = p1;

类型别名最常见的用途是在原类型声明过于笨拙时提供可方便管理的名称。模板通常会出现这种情况。标准库本身的一个示例是std::basic_string<T>来表示字符串。这是一个类模板,其中T是字符串中每个字符的类型,例如char。每当要引用模板类型参数时,都必须指明它。为了声明变量,指定函数参数等等,必须要写basic_string<char>。

cpp 复制代码
void processVector(const std::vector<std::basic_string<char>>& vec) {
	/* omitted */
}

int main() {
	std::vector<std::basic_string<char>> myVector;
	processVector(myVector);
}

因为basic_string<char>使用的频率如此之高,标准库便为其提供了一个更短也更为有意义的类型别名。

using string = basic_string<char>;

有了这个类型别名,之前的代码段可以被写得更加优雅。

cpp 复制代码
void processVector(const std::vector<std::string>& vec) {
	/* omitted */
}

int main() {
	std::vector<std::string> myVector;
	processVector(myVector);
}

2. 类型定义(不建议)

类型别名是在C++11引入的。在C++11之前,必须使用typedef完成类似的操作,但是要复杂得多。这里仍将解释这种旧机制,因为你将在遗留代码中看到它。

像类型别名一样,typedef为现有的类型声明提供新名称。例如,使用以下类型别名:

using IntPtr = int*;

可以用typedef将其改写为如下代码:

typedef int* IntPtr;

正如你所见,它的可读性降低了。顺序是颠倒的,即使对于专业的C++开发人员,也会引起很多混乱。除了更加复杂之外,typedef的行为与类型别名相同。例如,可以按如下方式使用typedef:

IntPtr p;

但是,类型别名和typedef并不完全等效。与typedef相比,类型别名与模板一起使用时功能更强大。

警告:总是优先选择类型别名而不是typedef。

3. 类型推断

3.1 auto

关键字auto有多种不同的用法(以下部分用法会在后面提及):

1. 推断函数的返回类型。

2. 结构化绑定。

3. 推断表达式的类型。

4. 推断非类型模板参数的类型。

5. 简写函数模板的语法。

6. decltype(auto)。

7. 其他函数语法。

8. 泛型lambda表达式。

auto可用于告诉编译器,在编译期自动推断变量的类型。示例如下:

auto x { 123 }; // x is of type int.

在这个示例中,输入auto和输入int的效果没有什么区别,但auto对较复杂的类型会更有用。假定getFoo()函数有一个复杂的返回类型。如果希望把调用该函数的结果赋予一个变量,可以输入该复杂类型,也可以简单地使用auto,让编译器推断出该类型。

auto result { getFoo() };

这样,可方便地更改函数的返回类型,而不需要更新代码中调用该函数的所有位置。

3.1.1 auto&

使用auto推断类型时去除了引用和const限定符。假设有以下函数:

cpp 复制代码
const std::string message { "Test" };
const std::string& foo() {
	return message;
}

可以调用foo(),把结果存储在一个变量中,将该变量的类型指定为auto,如下所示。

auto f1 { foo() };

因为auto去除了引用和const限定符,且f1是string类型,因此会建立一个副本。如果希望f1是一个const引用,就可以明确地将它建立为一个引用,并标记为const,如下所示。

const auto& f2 { foo() };

本章前面介绍了工具函数as_const(),它返回其引用参数的const引用版本。将as_const()与auto结合使用要小心。由于自动去除引用和const限定符,因此以下结果变量的类型为string,而不是const string&类型,因此将进行复制:

cpp 复制代码
std::string str { "C++" };
auto result { std::as_const(str) };

警告:始终要记住,auto去除了引用和const限定符,从而会创建副本!如果不需要副本,可使用auto&或者const auto&。

3.1.2 auto*

auto关键字也可以用于指针,示例如下:

cpp 复制代码
int i { 123 };
auto p { &i };

p的类型是int*。与上一节讨论的引用不同,此处不存在意外复制的危险。但是,在使用指针时,建议使用auto*语法,因为它可以更清楚地指出此处涉及指针。例如:

auto* p { &i };

此外,使用auto*代替auto确实可以解决将auto、const和指针一起使用时的奇怪行为。假设你编写如下内容:

const auto p1 { &i };

大多数情况下,不会发生你期待的事情!通常,当使用const时,你想要保护指针所指的东西。你可能认为p1的类型为const int*,但实际上,该类型为int* const,因此它是指向非const整数的const指针。按如下所示将const放在auto后面无济于事;类型仍然是int* const。

auto const p2 { &i };

当将auto*与const结合使用时,它的行为就会与期望的一样。这是一个例子:

const auto* p3 { &i };

现在p3的类型为const int*。如果你真的需要一个const指针而不是const的整数,需要将const放在后面。

auto* const p4 { &i };

最后,使用这个语法可以令指针和整数都是const。

const auto* const p5 { &i };

p5的类型是const int* const。如果省略了*,将不能得到这个结果。

3.1.3 拷贝列表初始化和直接列表初始化

有两种使用大括号初始化列表的初始化方式:

1. 拷贝列表初始化:T obj = { arg1, arg2, ... };

2. 直接列表初始化:T obj { arg1, arg2, ... };

与自动类型推断相结合,拷贝列表初始化和C++17引入的直接列表初始化之间就存在重要的区别。从C++17之后,你会得到如下结果(需要<initializer_list>)。

cpp 复制代码
// copy list initialization
auto a = { 11 }; // initializer_list<int>
auto b = { 11, 22 }; // initializer_list<int>

// direct list initialization
auto c { 11 ]; // int
auto d { 11, 22 }; // error, too many elements.

请注意,对于拷贝列表初始化,带括号的初始化程序中的所有元素都必须具有相同的类型。例如,以下内容会编译失败。

auto b = { 11, 22.33 }; // compilation error.

在早期的标准版本(C++11/14)中,拷贝列表初始化和直接列表初始化都将推断出initializer_list<>。

cpp 复制代码
// copy list initialization
auto a = { 11 }; // initializer_list<int>
auto b = { 11, 22 }; // initializer_list<int>

// direct list initialization
auto c { 11 ]; // initializer_list<int>
auto d { 11, 22 }; // initializer_list<int>

3.2 decltype

关键字decltype把表达式作为实参,计算出该表达式的类型。例如:

cpp 复制代码
int x { 123 };
decltype(x) y { 456 };

在这个示例中,编译器推断出y的类型是int,因为这是x的类型。

auto与decltype的区别在于,decltype未去除引用和const限定符。再来分析返回const string引用的foo()函数。按如下方式使用decltype定义f2,导致f2的类型为const string&,从而不生成副本。

cpp 复制代码
decltype(foo()) f2 { foo() };

刚开始不会觉得decltype有多大价值。但在模板环境中,decltype会变得十分强大。

4. 标准库简介

C++具有标准库,其中包含许多有用的类,在代码中可方便地使用这些类。使用标准库中类的好处是不需要重新创建某些类,也不需要浪费时间去实现系统已经自动实现的内容。另一好处是标准库中的类已经过成千上万用户的严格测试和验证。标准库中的类也经过了性能优化,因此使用这些类在大多数情况下比使用自己的类效率更高。

标准库中可用的功能非常多。后面将详细讲述标准库。当开始使用C++时,最好立刻了解标准库可以做什么。如果你是一位C程序员,这一点尤其重要。作为C程序员,使用C++可能会以C的方式解决问题,然而使用C++的标准库类可以更方便、安全地解决问题。

参考

比\] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C++20高级编程(第五版)

相关推荐
攻城狮7号17 分钟前
【第22节】windows网络编程模型(WSAAsyncSelect模型)
c++·windows·网络编程·windows编程·windows sdk
秋凉 づᐇ37 分钟前
每日一题--C与C++的差别
开发语言·c++
梅见十柒1 小时前
UNIX网络编程笔记:网络协议
服务器·网络·c++·经验分享·笔记·网络协议·unix
越甲八千1 小时前
C++常用多线程模式
开发语言·c++
佚明zj1 小时前
【设计模式】C++ 单例模式总结与最佳实践
开发语言·c++·单例模式
Absolute clown maste1 小时前
第十六届蓝桥杯康复训练--6
数据结构·c++·算法·蓝桥杯·动态规划
Source.Liu1 小时前
【CXX-Qt】2.4 嵌套对象
c++·qt·rust
DevangLic2 小时前
【3-22 list 详解STL C++ 】
c++·windows·list
残月只会敲键盘2 小时前
C++核心语法快速整理
开发语言·c++·算法
fzm52982 小时前
嵌入式软件单元测试的必要性、核心方法及工具深度解析
c语言·软件测试·c++·测试工具·单元测试