本章目录
4. 显示转换操作符
概念:
类型转换运算符:operator
类型转换操作符:禁止隐式转换explicit
C++中explicit关键字只能用于修饰只有一个参数的类构造函数 禁止隐式调用类内单参数构造函数。
- explicit 禁止隐式调用拷贝构造函数
- 禁止类对象之间隐式转换
- 只能用来修饰类内部的构造函数
class Test{
explicit Test(int a); // 禁止隐式调用
}
Test aa(10);//OK
Test aa = 10;//非法。加入explicit可以有效的防止隐式转换的发生,提高程序质量。
Test bb = aa;// 非法,取消了隐式转换,除非重载操作符“=”
为何要禁止隐式转换示例:
class Test{
public:
Test(int a);
bool isSame(Test other){
return m_val == other.m_val;
}
private:
int m_val;
}
Test a(10);
if(a.isSame(10));// 该语句将返回true
isSame本来用于比较两个对象,这里对象跟整形居然相等。是因为发生了隐式转换,实际上a是跟一个临时的Test对象进行比较。
示例2
class myDig {
int value;
public:
explicit myDig(int n) {
value = n;
}
}
void doSomething(myDig num); //函数,接受一个myDig参数,函数体不重要,故省略
void Test1() {
myDig dig1; // 错误,没有默认构造函数
doSomething(dig1); // 正确
myDig dig2(10); // 正确
doSomething(10); // 错误,myDig不支持隐式转换
doSomething(myDig(10)); //正确,调用**类型转换操作符**进行显示转换(也叫转型,cast), 调用myDig构造函数。
}
/*
* 如果去掉explicit则doSomething(10)正确, 执行了隐式转换
* 过程相当于执行 myDig tmp(10); doSomething(tmp);
*
* myDig dig3 = 10; //正确,也是隐式转换。
* /
四种显示转换:
参考
5. 列表初始化
C++98里允许对数据元素使用{} 进行初始值设定
int arr[5]={0};
int arr[]={1,2,3,4};
C++ 11 中拓展了初始化规则
int a[]={1,3,5};//C++98通过,C++11通过
int b[]{2,4,6};//C++98失败,C++11通过
vector<int> c{1,3,5};//C++98失败,C++11通过
map<int,float>d=
{{1,1.0f},{2,2.0f},{5,3.2f}};//C++98失败,C++11通过
定义在<initializer_list>头文件中
template< class T >
class initializer_list;
使用列表初始化可以防止类型收窄比如:
- 浮点数隐式转化为整数
- 高精度转化为低精度 long long -> long
- 整形转为浮点型,整形大到浮点数无法表示情况
- 整形转为较低长度整形,unsigned char = 1024
示例如下
const int x=1024;
const int y=10;
char a=x;//收窄,但可以通过编译
char*b=new char(1024);//收窄,但可以通过编译
char c={x};//收窄,无法通过编译
char d={y};//可以通过编译
unsigned char e{-1};//收窄,无法通过编译
float f{7};//可以通过编译
int g{2.0f};//收窄,无法通过编译
float*h=new float{1e48};//收窄,无法通过编译
float i=1.2l;//可以通过编译
6. POD类型
6.1 POD类型的定义和特性
POD : Plain Old Data 简单旧数据 指的是可以通过 简单内存复制 和传输的数据类型。POD对象可以通过memcpy或者其他等价操作进行复制,而且它们的内存布局是完全透明和可预测的。
C++ 中POD类型可以分为两类:trivial 和 standard layout 类型分别可以通过如下判断
template<typename T>struct std::is_trivial;
template <typename T> struct std::is_standard_layout;
Trivial类型
Trivial类型是一种简单的类型,它没有用户定义的构造函数、析构函数或复制操作符,没有私有或保护的非静态成员,没有基类,也没有虚函数。换句话说,trivial类型是一种没有任何特殊语义的类型,它的行为完全由其数据成员决定。例如,一个只包含基本类型(如int、char)成员的struct就是一个trivial类型。
满足如下条件
- 它的所有非静态成员都是Trivial类型。
- 它是一个类类型(class或struct),但没有用户定义的构造函数。
- 它没有虚函数和虚基类。
- 它没有非静态成员的类类型或数组,或者所有这些类类型和数组都是Trivial类型。
Standard layout类型
- 是一个类类型, 没有虚函数或者虚基类
- 所有非静态数据成员都具有相同访问控制
- 所有非静态数据成员和基类都是Standard layout类型
- 没有多个非静态数据成员的基类
- 所有非静态成员,包括在其所有基类中的非静态成员,都有相同的访问控制(public、private、protected)
以下是Standard layout类型示例:
class StandardLayout1 {
public:
int a;
char b;
};
struct StandardLayout2 {
public:
double x;
private:
StandardLayout1 y;
};
typedef int StandardLayout3[10];
6.2 POD类型重要性
-
与C语言兼容
使用POD类型可以方便的在C与C++之间进行传递数据。
例如,如果你有一个C语言的库函数,它接受一个指向结构体的指针,你可以在C++中定义一个相同的POD类型,然后传递给这个库函数。 -
序列化和网络通信
POD类型的内存布局是确定的,这使得它们非常适合用于序列化和网络通信。你可以直接将POD类型的对象写入文件或网络套接字,然后在另一端读取并重构对象。
例如,如果你有一个包含多个数据字段的POD类型,你可以将其序列化为一个字节流,然后通过网络发送到另一台计算机,或者写入文件以便以后读取。 -
性能优化
由于POD类型的对象可以通过简单的内存复制进行复制,因此,使用POD类型可以提高代码的性能。特别是在需要大量复制数据的场景中,使用POD类型可以显著减少CPU的负载。
例如,如果你有一个大数组,它的元素类型是POD类型,你可以使用std::memcpy函数一次性复制整个数组,这通常比逐个复制数组的元素要快得多。
7. 非受限联合体
联合体(union)是一种构造数据类型,在一个联合体内可以定义多个不同类型的成员,这些成员共享同一块内存空间。C++98不允许有非POD、静态、引用类型成员。C++11之后取消了联合体对数据成员的限制,任何非引用类型都可以成为联合体的数据成员。
示例:
struct Student{
Student(bool g, int a): gender(g), age(a){}
bool gender;
int age;
};
union T{
Student s; //C++98下编译失败,不是一个POD类型
int id;
char name[10];
};
int main(){
return 0;
}
//编译选项:g++ -std=c++98 union.cpp
注意事项:
C++11 规定:如果非受限联合体内有非POD的成员,则该成员拥有自定义的构造函数,那么这个非受限联合体的默认构造函数将被编译器删除;其他特殊成员如拷贝构造、拷贝赋值操作符及析构函数也被删除。
#include <string>
using namespace std;
union U {
string s;
int n;
};
int main() {
U u; // 构造失败,因为 U 的构造函数被删除
return 0;
}
因为string拥有自己的构造函数,所以U联合体的默认构造函数被删除;修改如下:
#include <string>
using namespace std;
union U {
string s;
int n;
public:
U() { new(&s) string; }
~U() { s.~string(); }
};
int main() {
U u;
return 0;
}
8. 用户自定义字面量
含义:通过一个后缀标识符,将声明了该后缀标识的字面量转换为需要的相应类型。
示例:
#include<iostream>
int operator "" _h(unsigned long long hour)//注意这里参数必须是unsigned long long类型(可以硬记){
return hour*60*60;//返回秒
}
int operator "" _m(unsigned long long min){
return min * 60;
}
int operator "" _s(unsigned long long s){
return s;
}
int main(){
std::cout<<1_h<<" "<<1_m<<" "<<1_s;//相当于单位转换的意思
return 0;
}
注意:
- operator"" 与自定义后缀之间必须有空格
- 后缀建议以下划线开始,否则会引起编译器警告
- C++11 标准中要求声明字面量操作符要遵循如下规则
- 字面量为整数值接受 unsigned long long 和 const char *
- 字面量为浮点数接受 unsigned double 或者 const char
- 字面量为字符串接受 const char* size_t为参数
- 字面量为字符接受 char为参数
9. 内联命名空间
嵌套命名空间
示例:
namespace S{
// 第一个嵌套的命名空间,定义了class A
namespace S1{
class A {};
}
// 第二个嵌套的命名空间,定义了class B
namespace S2{
class B {};
}
}
S::S1::A // 访问
内联命名空间
含义:内联命名空间中的名字可以被外层命名空间直接使用。
#include <iostream>
namespace Parent{
namespace Child1{
void foo() { std::cout << "Child1::foo()" << std::endl; }
}
inline namespace Child2{
void foo() { std::cout << "Child2::foo()" << std::endl; }
}
}
int main(){
Parent::Child1::foo();
Parent::foo();// 无需添加该命名空间的前缀,直接被外层命名空间使用
}
可以解决库函数升级的问题 如:
#include <iostream>
namespace Parent{
void foo() { std::cout << "foo v1.0" << std::endl; }
}
int main(){
Parent::foo();
}
升级之后
namespace Parent{
namespace V1{
void foo() { std::cout << "foo v1.0" << std::endl; }
}
inline namespace V2{
void foo() { std::cout << "foo v2.0" << std::endl; }
}
}
int main(){
Parent::foo(); // 如果向继续使用V1老接口Parent::V1::foo();
}
10. 模版的别名
C++ 11中新增using用法来定义模版别名
template< typename first, typename second, int third>
class SomeType;
template< typename second>
typedef SomeType<OtherType, second, 5> TypedefName; // 在C++是不合法的
这不能够通过编译。
为了定义模板的别名,C++11 将会增加以下的语法:
template< typename first, typename second, int third>
class SomeType;
template< typename second>
using TypedefName = SomeType<OtherType, second, 5>;
using 也能在 C++11 中定义一般类型的别名,等同 typedef:
typedef void (*PFD)(double); // 傳統語法
using PFD = void (*)(double); // 新增語法
11. 一般化的SFINEA规则
在C++模板中,有一条著名的规则,即SFINEA-Substitution failure is not an error,中文直译即是“匹配失败不是错误”。