C++11特性的学习之保证稳定性和兼容性(一)

  目录括号内为适合人群,所有库作者的内容暂不做学习,可自行查阅《深入理解C++11:C++11新特性解析与应用》。网盘链接: https://pan.baidu.com/s/1Jf29R7-foOoXJ5UW3mTKVA 密码: 7vgq

目录

1.保持与C99兼容(部分人)
  ①预定义宏
  ②_func_预定义标识符
  ③_Pragma操作符
  ④变长参数的宏定义以及_VA_ARGS_
  ⑤宽窄字符串连接
2.long long 整型(部分人)
3.扩展的整型(部分人)
4.宏__cplusplus(部分人)
5.静态断言(库作者)
  ①断言:运行时与预处理时
  ②静态断言与static_assert
6.noexcept修饰符与noexcept操作符(库作者)
7.快速初始化成员变量(部分人)
8.非静态成员的sizeof(部分人)
9.扩展的friend语法(部分人)
10.final/override控制(部分人)
11.模板函数的默认模板参数(所有人)
12.外部模板(部分人)
  ①为什么需要外部模板
  ②显式的实例化与外部模板的声明
13.局部和匿名类型作模板实参(部分人)

1.保持与C99兼容 ^

 ①预定义宏 ^

宏名称功能描述
__STDC_HOSTED__如果编译器的目标系统环境中包含完整的标准C库,那么这个宏就定义为1,否则宏的值为0
__STDC__C编译器通常用这个宏的值来表示编译器的实现是否和C标准一致。这个宏是否定义以及定义成什么值由编译器决定
__STDC_VERSION__C编译器通常用这个宏来表示所支持的C标准的版本。这个宏是否定义以及定义成什么值由编译器决定
__STDC_ISO_10646__这个宏通常定义为一个yyyymmL格式的整数常量,如199712L,用来表示C++编译环境符合某个版本的ISO/IEC 10646标准

  预定义宏对于多目标平台代码的编写通常具有重大意义。通过以上的宏,我们可以通过使用#ifdef#endif等预处理指令,就可使得平台相关代码只在适合于当前平台的代码上编译,从而在同一套代码中完成对多平台的支持。

#include <iostream>
using namespace std;

int main(){
    cout<<"是否有标准C库"<<__STDC_HOSTED__<<endl;//输出1
    //后三个宏在我的编译器上未定义
}

 ②_func_预定义标识符 ^

  __func__预定义标识符,其基本功能就是返回所在函数的名字

#include <iostream>
using namespace std;
const char* hello(){ return __func__;}
const char* world(){ return __func__;}
int main(){
    cout<<hello()<<" "<<world()<<endl;//输出hello world
}

  __func__预定义标识符对于轻量级的调试代码具有十分重要的作用。在C++11中,标准允许其使用在类或结构体中

#include <iostream>
using namespace std;
class helloWorld {
public:
    const char* hello() { return __func__; }
    const char* world() { return __func__; }
};
int main(){
    helloWorld h;
    cout << h.hello() << " " << h.world() << endl;//输出hello world
}

注:__func__标识符作为参数的默认值是不允许的

 ③_Pragma操作符 ^

  在C/C++标准中,#pragma是一条预处理的指令,简单的说是用来向编译器传达语言标准以外的一些信息。如:

#pragma once //指示编译器该头文件应该只被编译一次
//等同于
#ifndef THIS_HEADER
#define THIS_HEADER
//一些头文件的定义
#endif

  在C++11中,标准定义了与预处理指令#pragma功能相同的操作符_Pragma,格式为_Pragma (字符串字面值),#pragma once等同于_Pragma (“once”);

  相比预处理指令#pragma,由于_Pragma是一个操作符,所以可以用在一些宏中,具有更大的灵活性

 ④变长参数的宏定义以及__VA_ARGS__ ^

  在C99标准中,我们可以使用变长参数的宏定义。变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串。如:

##define PR(...) printf(__VA_ARGS__)
#define LOG(...) {\
    fprintf(stderr,"%s:Line %d:\t",__FILE__,__LINE__);\
    fprintf(stderr,__VA_ARGS__);\
    fprintf(stderr,"\n");\
}
int main()
{
    PR("这是一个__VA_ARGS__");//输出:这是一个__VA_ARGS__
    LOG("当前所在位置");//输出:当前文件所在位置+所在行+参数值(当前所在位置)
}

 ⑤宽窄字符串连接 ^

  在之前的C++标准中,将窄字符串(char)转换成宽字符串(wchar_t)是未定义的行为。而在C++11标准中,在将窄字符串和宽字符串进行连接时,支持C++11标准的编译器会将窄字符串转换成宽字符串,然后再与宽字符串进行连接

2.long long 整型 ^

  long long 整型有两种:long long 和 unsigned long long 。在C++11中,标准要求long long 整型可以在不同平台上有不同的长度,但至少有64位。我们在写常数字面值时,可以使用LL后缀(或是ll)标识一个long long 类型的字面值,而ULL(或ull,Ull,uLL)表示一个unsigned long long 类型的字面值,如:

long long int a=-45121454562LL; 
unsigned long long int b=45124845ULL; 

  在C++11中,有很多与long long 等价的类型,对于有符号的,下面的类型是等价的:long long,long long int,signed long long,signed long long int;对于无符号:unsigned long long 和unsigned long long int也是等价的

  要了解平台上long long 大小的方法是查看climits头文件中的宏。与long long 整型相关的一共有3个:LLONG_MAX,LLONG_MIN和ULLONG_MAX分别表示long long类型的最大值,最小值,以及unsigned long long 类型的最大值

cout <<"long long 最大值:"<< LLONG_MAX<<" long long 最小值:"<<LLONG_MIN<<" unsigned long long 最大值:"<<ULLONG_MAX<<endl;

3.扩展的整型 ^

  在C++11中一共定义了5中标准的有符号整型:①signed char ②short int ③int ④long int ⑤long long int。标准同时规定,每一种有符号整型都有一种对于的无符号整数版本,且有符号整数与其对应的无符号整型具有相同的存储空间大小。

  在实际编程中,由于中五种基本的整型适用性有限,所以有时编译器出于需要,会自行扩展一些整型,如int16_t,UINT等。C++11标准并没有对扩展出的类型的名称有任何的规定或建议,只是对扩展整型的使用规则做出了一定的限制,即扩展的整型必须和标准类型一样,有符号类型和无符号类型占用同样大小的内存空间。整数间发生隐式的转换时,也必须遵守相应的原则。

4.宏__cplusplus ^

  在C与C++混合编写的代码中,我们常常会在头文件里看到如下的声明:

#ifdef __cplusplus
extern "C"{
#endif
//一些代码
#ifdef __cplusplus
}
#endif

  这种类型的头文件可以被#include到C文件中进行编译,也可以被#include到C++文件中进行编译。__cplusplus这个宏通常被定义为一个整型值,随着标准变化,__cplusplus宏一般会是一个比以往标准中更大的值。比如在C++03标准中,__cplusplus的值被预定为199711L,而在C++11标准中,宏__cplusplus被预定义为201103L。

5.静态断言 ^

  暂不作学习,可查阅《深入理解C++11:C++11新特性解析与应用》。

 ①断言:运行时与预处理时 ^

 ②静态断言与static_assert ^

  

6.noexcept修饰符与noexcept操作符 ^

  暂不作学习,可查阅《深入理解C++11:C++11新特性解析与应用》。

7.快速初始化成员变量 ^

  在C++98中,支持了在类声明中使用等号“=”加初始值的方式,来初始化类中静态成员常量。这种声明方式称之为“就地”声明。而在C++11中,标准允许非静态成员变量的初始化有多种形式,如等号=或者花括号{ }进行就地的非静态成员变量初始化,如:

class A
{
public:
    A(int _a,string _str):a(_a),str(_str){}//C++98标准中初始化方式,初始化列表
private:
    int a=1;//C++11初始化方式,等号=
    string str {"nihao"};//C++11初始化方式,花括号{}
};

  相对于传统的初始化列表,在类声明中对非静态成员变量进行就地列表初始化可以降低我们的工作量。当我们的类有多个构造函数,且有多个成员变量的时候,用C++11中对非静态成员变量的就地初始化,可以避免重复地在初始化列表中写上每个非静态成员了

  对于非常量的静态成员变量,C++11则与C++98保持一致,需要到头文件以外去定义它。

8.非静态成员的sizeof ^

  在C++98标准中,只有静态成员,或者对象的实例才能对类成员进行sizeof操作。在C++11标准中,允许用类成员表达式对类成员进行sizeof操作。

class Person
{
public:
    string name{"mingzi"};

};
sizeof(Person::name);//c++98中错误,C++11中通过

9.扩展的friend语法 ^

  在C++98中,声明一个类为另一个类的友元时,需要加上关键字class,否则编译不通过,而C++11中,则可以直接省略关键字class,甚至可以使用类的别名。

class A;
typedef A a;
class B
{
    friend class A; //C++98通过,C++11通过
    friend A;       //C++98失败,C++11通过
    friend a;       //C++98失败,C++11通过
}

  由于这一个小改进,我们现在可以为类模板声明友元了。


class A;
template <class T> class B
{
    friend T;
};
B<A> b;       //在这里,类型A是B的友元
B<int> bb;    //对于内置类型模板参数,友元声明被忽略

  对于模板参数为内置类型的模板类,在实例化时友元声明将被忽略,而模板参数为类类型的模板类,实例化时可知该模板参数为该模板类的一个友元类。因此,我们只有在模板实例化时才确定一个模板类是否有友元,以及谁是这个模板类的友元。一个简单的例子:

template <class T> class A
{
    //私有成员
    friend T;//为类模板声明友元
    string name{ "zhangsan" };
    int age{ 23 };
};
class B
{
public:
    void outPut(A<B> &a) { cout << "姓名:"<<a.name<<" 年龄:"<<a.age; }
};
int main()
{
    A<B> a;
    B b;
    b.outPut(a);//输出:姓名:zhangsan 年龄:23
}

10.final/override控制 ^

  在C++98中,一个类A中的成员函数print被声明为virtual,那么对于其派生类B而言,print总是能够被重载的(即函数原型一样),如果我们希望只有这么一个版本,不被后续的派生类所重写,在C++98标准中是无法阻止的

class Person
{
public:
    virtual void print()=0;//纯虚函数,该类只能被继承,不能实例化
};

class Person2:public Person
{
public:
    void print(){ cout << "姓名:" << name << "年龄" << age; }
private:
    string name{ "张三" };
    int age{ 23 };
};

class Person3:public Person2
{
public:
    //Person2中的print被重写了,在C++98中无法阻止该接口被重写
    void print() { cout << "姓名:" << name << endl << "年龄" << age; }
private:
    string name{ "李四" };
    int age{ 25 };
};

  在C++11中,可以使用final关键字来阻止函数继续被重写。final关键字的作用是使派生类不可覆盖它所修饰的虚函数,格式为在函数参数列表之后,例:

class Person
{
public:
    virtual void print()=0;//纯虚函数,该类只能被继承,不能实例化
};

class Person2:public Person
{
public:
    void print() final { cout << "姓名:" << name << "年龄" << age; }
private:
    string name{ "张三" };
    int age{ 23 };
};

class Person3:public Person2
{
public:
    //由于Personx2中的print函数被final修饰了,所以在其派生类中重写print将无法通过编译
    void print() { cout << "姓名:" << name << endl << "年龄" << age; }
private:
    string name{ "李四" };
    int age{ 25 };
};

  在C++中重载有一个特点,就是对于基类声明为virtual的函数,之后的重载版本都不需要声明该重载函数为virtual。这带来了一些书写上的便利却也带来了阅读上的困难。在多层次的继承关系中,底层的派生类无从得知继承来的函数是虚函数还是非虚函数。为此C++11引入了虚函数描述符override,如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。例:

class Person
{
public:
    virtual void print()=0;
    virtual void getName();
    virtual void setName(string str);
    virtual void getAge() const;
    void setAge(int a);
};

class Person2:public Person
{
public:
    void print() override;          
    void tegName() override;          //无法通过编译,拼写错误,并非重载
    void setName(int str) override;   //无法通过编译,参数不一致,并非重载
    void getAge() override;           //无法通过编译,常量性不一致,并非重载
    void setAge() override;           //无法通过编译,非虚函数重载
    }

  我们的本意是重写虚函数,在没有override修饰的情况下,我们的书写错误将被编译器当成声明一个新函数,这明显不符合我们的本意。所以在有override修饰的情况下,可以让编译器辅助的做一些检查,剔除这些情况。

11.模板函数的默认模板参数 ^

  在C++11中模板和函数一样,可以有默认的参数。类模板在有多个默认模板参数声明指定默认值的时候,必须遵循“从右往左”的规则进行指定。而对于函数模板来说并不是必须的,例:

//类模板
template <class T1=int,class T2> class A;//编译无法通过
template <class T1,class T2=int> class A;//编译通过

//函数模板
template <class T1=int,class T2> void print();//编译通过

12.外部模板 ^

 ①为什么需要外部模板 ^

  “外部模板”是C++11中一个关于模板性能上的改进。“外部”(extern)这个概念早在C的时候就已经有了,关键字extern就是用来外部变量的声明:extern int i。这样做的好处是,在分别编译多个文件后,在其生成的目标文件比如a.o和b.o中只有一份定义。如果没有extern,则a.0和b.0中各有一份i的定义,链接器在链接a.o和b.o时就会报错,因为无法决定相同的符号是否需要合并。

  对于函数模板来说,遇到了几乎一模一样的问题,不同的是,发生问题的不是变量而是函数。比如:

//test.h
template <class T> void fun(T t){}

//test1.cpp
#include "test.h"
void test1(){ fun(3); }

//test2.cpp
#include "test.h"
void test2(){ fun(5); }

  由于两个源代码使用的模板函数的参数类型一致,所以在编译test1.cpp时,编译器实例化出函数fun>int>(int),而编译text2.cpp时再一次实例化出fun>int>(int),造成代码重复。为了解决这一问题,就需要使用“外部模板”

 ②显式的实例化与外部模板的声明 ^

  外部模板的使用依赖于C++98中已有的特性,即显示实例化。比如:

//对于这个模板
template <class T> void fun(T) {}
//我们只需要声明:
template void fun<int>(int);

  在C++11标准中,加入了外部模板的声明,语法上只是在显示实例化的声明前加一个extern,即

extern template void fun<int>(int);//外部模板的声明

注:外部模板声明不能用于一个静态函数,但可以用于类静态成员函数

13.局部和匿名类型作模板实参 ^

  在C++98中,标准对目标实参的类型还有一些限制,局部的类型和匿名的类型都不能做模板类的实参。但是在C++11中,标准允许了以上类型做模板参数的做法。虽然匿名类型可以被模板参数所接受,但并不意味着以下写法可以被接受:

template <class T> struct Temp{};
int main()
{
    Temp<struct { int a; } > t;//无法通过编译,匿名函数的声明不能再模板实参位置
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值