第3章 通用性【下】

4. 显示转换操作符

概念:
类型转换运算符:operator
类型转换操作符:禁止隐式转换explicit
C++中explicit关键字只能用于修饰只有一个参数的类构造函数 禁止隐式调用类内单参数构造函数。

  1. explicit 禁止隐式调用拷贝构造函数
  2. 禁止类对象之间隐式转换
  3. 只能用来修饰类内部的构造函数
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;

使用列表初始化可以防止类型收窄比如:

  1. 浮点数隐式转化为整数
  2. 高精度转化为低精度 long long -> long
  3. 整形转为浮点型,整形大到浮点数无法表示情况
  4. 整形转为较低长度整形,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类型。
满足如下条件

  1. 它的所有非静态成员都是Trivial类型。
  2. 它是一个类类型(class或struct),但没有用户定义的构造函数。
  3. 它没有虚函数和虚基类。
  4. 它没有非静态成员的类类型或数组,或者所有这些类类型和数组都是Trivial类型。

Standard layout类型

  1. 是一个类类型, 没有虚函数或者虚基类
  2. 所有非静态数据成员都具有相同访问控制
  3. 所有非静态数据成员和基类都是Standard layout类型
  4. 没有多个非静态数据成员的基类
  5. 所有非静态成员,包括在其所有基类中的非静态成员,都有相同的访问控制(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类型重要性

  1. 与C语言兼容
    使用POD类型可以方便的在C与C++之间进行传递数据。
    例如,如果你有一个C语言的库函数,它接受一个指向结构体的指针,你可以在C++中定义一个相同的POD类型,然后传递给这个库函数。

  2. 序列化和网络通信
    POD类型的内存布局是确定的,这使得它们非常适合用于序列化和网络通信。你可以直接将POD类型的对象写入文件或网络套接字,然后在另一端读取并重构对象。
    例如,如果你有一个包含多个数据字段的POD类型,你可以将其序列化为一个字节流,然后通过网络发送到另一台计算机,或者写入文件以便以后读取。

  3. 性能优化
    由于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;
}

注意:

  1. operator"" 与自定义后缀之间必须有空格
  2. 后缀建议以下划线开始,否则会引起编译器警告
  3. 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,中文直译即是“匹配失败不是错误”。

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值