C++学习:C++11关于类型的处理

之前我们已经学习了对于C++类型处理的相关知识。作为一个以类型安全为宗旨的语言,对于数据类型的认识是十分重要的。本期我们就来学习一下C++11中更好用的类型表达及其对数据类型的处理。

作者的个人gitee:https://gitee.com/riko-lou-tian/cpp-code-learning

喜欢请支持一下谢谢

目录

auto

decltype

尾置返回类型

基本语法

为什么需要尾置返回类型

使用场景

typedef和using


auto

前⾯课程中我们已经⽤过auto,auto是⼀个类型说明符,他让编译器替我们分析表达式的类型,auto x = y + z; 编译器⾃动根据y+z相加的结果来推导x的类型,在⼀些类型⽐较⻓的场景,如前⾯讲的迭代器遍历时⾮常有⽤。

编译器推导auto类型时,有时候也会和初始值的类型不⼀样,编译器会适当的改变结果类型,使其更符合初始化规则。⾸先使⽤引⽤其实是使⽤引⽤的对象,特别是当引⽤被⽤作初始值时,真正参与初始化的其实是引⽤对象的值 ,所以编译器推导auto为引⽤对象的类型,⽽不是引⽤。其次⼀个带有const属性的值初始化auto对象推导时忽略掉顶层const,保留底层const

auto不能⾃动推导出引⽤类型,所以我们如果想将auto推导为引⽤类型,需要明确的指出:

cpp 复制代码
auto& x = i;

auto不能推导出顶层const,如果想使⽤auto推导出顶层const,需要明确的指出:

cpp 复制代码
const auto x = ci;

设置⼀个类型为auto引⽤时,初始值中的顶层const属性仍然保留,否则存在权限放⼤问题。

cpp 复制代码
#include <iostream>

using namespace std;
int main()
{
	int i = 0;
	int& ri = i;
	const int ci = 42; // 顶层const
	int* const p1 = &i; // 顶层const
	const int* p2 = &ci; // 底层const
	const int& ri1 = ci; // 底层const
	const int& ri2 = i; // 底层const
	auto j = ri; // j类型为int
	j++;
	auto k = i; // k类型为int
	k++;
	auto r1 = ci; // r1类型为int,忽略掉顶层const
	r1++;
	auto r2 = p1; // r2类型为int*,忽略掉顶层const
	r2++;
	auto r3 = p2; // r3类型为const int*,保留底层const
	// (*r3)++; // 报错
	auto r4 = ri1; // r4类型为int,因为ri1是ci的别名,ci本⾝的const是⼀个顶层const被忽略掉了
	r4++;
	auto r5 = ri2; // r5类型为int
	r5++;
	const auto r7 = ci; // r7类型为const int
	auto& r8 = ri1; // r8类型为const int&
	auto& r9 = ri2; // r9类型为const int&
	auto& r10 = ci; // r10类型为const int&
	auto& r11 = ri; // r11类型为int&
	//r7++; // 报错
	//r8++; // 报错
	//r9++; // 报错
	//r10++; // 报错
	r11++;
	return 0;
}

auto& 声明⼀个左值引⽤,它只能绑定到左值,如果初始化对象有const属性,推导时会保持

const 限定符,否则涉及权限放⼤。

const auto& 声明⼀个const 左值引⽤,既可以绑定到左值⼜可以绑定到右值,不会修改绑定

对象。

auto&& 是万能引⽤,遵循引⽤折叠的规则,既可以绑定到左值⼜可以绑定到右值,初始化表达式⾃动推导为左值引⽤或右值引⽤,如果初始化对象有const属性,推导时会保持 const 限定符。

注意:auto不可以定义成员变量!

测试代码:

cpp 复制代码
#include <iostream>
#include<vector>
using namespace std;

void func(int& x)
{
	cout << "void func(int& x)" << endl;
} 
void func(int&& x)
{
	cout << "void func(int&& x)" << endl;
} 
void func(const int& x)
{
	cout << "void func(const int& x)" << endl;} 
void func(const int&& x)
{
	cout << "void func(const int&& x)" << endl;
} 
int main()
{
	int x = 10;
	const int cx = 20;
	auto& rx1 = x; // int&
	auto& rx2 = cx; // const int&
	func(rx1);
	func(rx2);
	const auto& rx3 = x; // const int&
	const auto& rx4 = cx; // const int&
	func(rx3);
	func(rx4);
	// 万能引⽤
	auto&& rx5 = x; // int&
	auto&& rx6 = cx; // const int&
	func(rx5);
	func(rx6);
	auto&& rx7 = move(x); // int&&
	//rx7++;
	auto&& rx8 = move(cx); // const int&&
	//rx8++;
	func(forward<int>(rx7));
	func(forward<const int>(rx8));
	return 0;
}

结果为:

decltype

如果我们希望⽤表达式推出变量的类型,但是不想⽤表达式的值初始化变量,那么这时可以使⽤decltype。

cpp 复制代码
 decltype(f()) x;

需要注意的是编译器并不会实际调⽤f函数,⽽是⽤f的返回类型作为x的类型。

decltype 处理const和引⽤的⽅式和auto也有所不同,

cpp 复制代码
 decltype(const变量表达式) x 

x的类型推出类型为const T,decltype会保留顶层const; decltype(引⽤变量表达式) x ,x的

类型推出类型为T &,decltype会保留引⽤;要注意这⾥跟auto是完全不同的。

decltype还有⼀些特殊处理⽐较奇怪,

cpp 复制代码
decltype(*p) x;

x的类型是T&,decltye推导解引⽤表达式时,推出类型是引⽤;

cpp 复制代码
 decltype((i)) x;

x的类型是T&,decltye推导解括号括起来的左值表达式时,推出类型是引⽤;

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
	int i = 0;
	const int ci = 0;
	const int& rci = ci;
	decltype(i) m = 1; // m的类型是int
	decltype(ci) x = 1; // x的类型是const int
	m++;
	x++; // 报错
	decltype(rci) y = x; // y的类型是const int&
	y++; // 报错
	decltype(rci) z; // 报错
	int* p1 = &i;
	decltype(p1) p2 = nullptr; // p2的类型是int*
	// 特殊处理
	decltype(*p1) r1 = i; // r1的类型是int&,解引⽤表达式推导出的内容是引⽤
	decltype(i) r2;
	// r3的类型是int&, (i)是⼀个表达式,变量是⼀种可以赋值特殊表达式,
	// 所以会推出引⽤类型
	decltype((i)) r3 = i; 
	
	return 0;
}

auto必须要通过初始化值推导类型,像类的成员变量这种就没办法使⽤auto,decltype可以很好的解决这样的问题

cpp 复制代码
#include <iostream>
#include<vector>
using namespace std;
template <typename T>
class A
{
	public :
	void func(T& container)
	{
		_it = container.begin();
	}
private:
	// 这⾥不确定是iterator还是const_iterator,也不能使⽤auto
	//typename T::iterator _it;
	// 使⽤decltype推导就可以很好的解决问题
	decltype(T().begin()) _it;
};
int main()
{
	const vector<int> v1;
	A<const vector<int>> obj1;
	obj1.func(v1);
	vector<int> v2;
	A<vector<int>> obj2;
	obj2.func(v2);
	return 0;
}

decltype可以用来实例化lambda表达式的类型进行赋值

cpp 复制代码
int main()
{
	//decltype在lambda表达式中也能用的应用
	auto lambda1 = [](int x, int y) { return x + y; };
	decltype(lambda1) lambda2 = lambda1;  // 复制构造

	// 使用
	std::cout << lambda1(5, 3) << std::endl;  // 8
	std::cout << lambda2(5, 3) << std::endl;  // 8
	return 0;
}

decltype还可以⽤来解决函数尾置返回类型的问题,有时⼀个函数模板的类型跟是不确定的,跟某个参数对象有关,需要进⾏推导,直接⽤decltype推导去做返回类型是不⾏了,因为C++是前置语法,往前找不到这个推导对象,这个对象在参数⾥⾯,所以auto做返回值,然后->decltype(对象)做尾置推导

cpp 复制代码
template<class R, class Iter>
R Func(Iter it1, Iter it2)
{
	R x = *it1;
	++it1;
	while (it1 != it2)
	{
		x += *it1;
		++it1;
	} 
	return x;
} 
// 不⽀持这样写,因为C++是前置语法,编译器遇到对象只会向前搜索
//template<class Iter>
//decltype(*it1) Func(Iter it1, Iter it2)
//{
// auto& x = *it1;
// ++it1;
// while (it1 != it2)
// {
// x += *it1;
// ++it1;
// }
// return x;
//}
// 返回位置⽤auto,函数后⾯接->推导返回类型的位置返回值⽅式
// 要注意decltype(*it1)推导出的是引⽤类型
//template<class Iter>
//auto Func(Iter it1, Iter it2)->decltype(*it1)
//{
// auto& x = *it1;
// ++it1;
// while (it1 != it2)
// {
// x += *it1
// ++it1;
// }
// return x;
//}
int main()
{
	vector<int> v = { 1,2,3 };
	list<string> lt = { "111","222","333" };
	// 这⾥⽆法调⽤上⾯的函数,因为函数模板只能通过实参推导模板类型,⽆法推导R
	//auto ret1 = Func(v.begin(), v.end());
	//auto ret2 = Func(lt.begin(), lt.end());
	// 显⽰实例化能解决问题,但是调⽤就很⿇烦
	auto ret1 = Func<decltype(*v.begin())>(v.begin(), v.end());
	auto ret2 = Func<decltype(*lt.begin())>(lt.begin(), lt.end());
	cout << ret1 << endl;
	cout << ret2 << endl;
	return 0;
}

decltype(auto) 是 C++14 引⼊的特性,它结合了 auto 的便利性和 decltype 的精确类型推导能⼒。

cpp 复制代码
//decltype(auto) 
int main()
{
	int i = 0;
	int& ri = i;
	const int ci = 42; // 顶层const
	int* const p1 = &i; // 顶层const
	const int* p2 = &ci; // 底层const
	auto j = ri; // j类型为int
	decltype(auto) j1 = ri; // j1类型为int&
	++j1;
	auto r1 = ci; // r1类型为int,忽略掉顶层const
	decltype(auto) rr1 = ci; // rr1类型为const int
	r1++;
	//rr1++;
	auto r2 = p1; // r2类型为int*,忽略掉顶层const
	decltype(auto) rr2 = p1; // rr1类型为int* const
	r2++;
	//rr2++;
	auto r3 = p2; // r3类型为const int*,保留底层const
	decltype(auto) rr3 = p2; // rr3类型为const int*
	// (*rr3)++;
	return 0;
}

尾置返回类型

尾置返回类型是C++11引⼊的⼀种函数声明语法,它允许将函数的返回类型放在参数列表之后⽽不是函数名前。尾置返回类型的语法这⾥我们简单的做个了解即可,因为C++14引⽤了auto做返回类型时,返回类型⾃动推导,很多地⽅就不太需要尾置返回类型了

基本语法

cpp 复制代码
auto functionName(parameters) -> returnType 
{
    // 函数体
}

为什么需要尾置返回类型

  1. 提⾼代码可读性:特别是当返回类型很⻓或复杂时

  2. ⽀持Lambda表达式:Lambda表达式的返回类型必须使⽤尾置语法

  3. 模板编程:在模板函数中,返回类型可能依赖于参数类型

使用场景

cpp 复制代码
// 1. 复杂返回类型
auto getComplexType() -> std::map<std::string, std::vector<int>> {
	// ...
}
// 2. 依赖参数类型的返回类型
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
	return t + u;
}
// 3. lambda表达式
auto lambda = [](int x) -> double { return x * 1.5; };

typedef和using

C++98中我们⼀般使⽤typedef重定义类型名,也很⽅便,但是typedef不⽀持带模板参数的类型重定义。C++11中新增了using可以替代typedef,using 的别名语法覆盖了 typedef 的全部功能,不少场景还更清晰⼀些,⽐如函数指针的重定义,其次最⼤的变化是⽀持带模板参数重定义的语法。

语法格式 :

cpp 复制代码
using 类型别名 = 类型;
cpp 复制代码
#include<iostream>
#include <map>
#include <string>
using namespace std;
//typedef map<string, int> CountMap;
//typedef map<string, string> DictMap;
//typedef int DateType;
// typedef void (*Callback)(int);
// using 兼容typedef的⽤法
using CountMap = map<string, int>;
using DictMap = map<string, string>;
using STDateType = int;
using Callback = void (*)(int);
// using⽀持带模板参数的类型重定义
template<class Val>
using Map = map<string, Val>;
template<class Val>
using MapIter = typename map<string, Val>::iterator;
int main()
{
	Map<int> countMap;
	Map<string> dictMap;
	MapIter<int> countIter = countMap.begin();
	MapIter<string> dictIter = dictMap.begin();
	return 0;
}

本期内容就到这里了,喜欢请点个赞谢谢

封面图自取:

相关推荐
LSTM975 小时前
使用 Java 对 PDF 添加水印:提升文档安全与版权保护
后端
该用户已不存在5 小时前
Gemini CLI 扩展,把Nano Banana 搬到终端
前端·后端·ai编程
用户298698530145 小时前
Spire.Doc 实践指南:将Word 文档转换为 XML
后端·.net
LCG元5 小时前
Docker容器化实战:将你的SpringBoot应用一键打包部署(二)-设置CI/CD流水线实现自动化部署
后端·docker
用户4099322502125 小时前
想让PostgreSQL查询快到飞起?分区表、物化视图、并行查询这三招灵不灵?
后端·ai编程·trae
Value_Think_Power5 小时前
每次请求时,后端先对比过期时间,如果过期就refresh
后端
酷讯网络_2408701605 小时前
PHP双轨直销企业会员管理系统/购物直推系统/支持人脉网络分销系统源码
学习·开源
用户68545375977695 小时前
🛡️ MyBatis的#{}和${}:安全 vs 危险!
后端
uhakadotcom5 小时前
ChatGPT Atlas的使用笔记
后端·面试·github