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;
}

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

封面图自取:

相关推荐
71-316 分钟前
C语言练习题——判断水仙花数(0-100000)
c语言·笔记·学习
FAREWELL0007525 分钟前
Lua学习记录(3) --- Lua中的复杂数据类型_table
开发语言·学习·lua
Felix_XXXXL34 分钟前
Plugin ‘mysql_native_password‘ is not loaded`
java·后端
IT北辰36 分钟前
Python实现居民供暖中暖气能耗数据可视化分析(文中含源码)
开发语言·python·信息可视化
韩立学长36 分钟前
【开题答辩实录分享】以《基于SpringBoot在线小说阅读平台》为例进行答辩实录分享
java·spring boot·后端
Broken Arrows43 分钟前
排查网络问题的一些工具的作用和常用使用方法
linux·网络·学习
KWTXX44 分钟前
组合逻辑和时序逻辑的区别
java·开发语言·人工智能
wjs20241 小时前
Go 语言结构体
开发语言
程序猿小蒜1 小时前
基于SpringBoot的企业资产管理系统开发与设计
java·前端·spring boot·后端·spring
jzhwolp1 小时前
从基本链表到侵入式链表,体会内核设计思路
c语言·后端·设计模式