C++新增和有变化的关键字


前言


c++文章连载:
1.C++基础
  1.C++基础
  2.C++新增和有变化的关键字
  3.C++的内存管理
2.面向对象
  1.C++的封装和访问权限
  2.C++继承和多态特性
  3.C++的运算符重载
  4.C++静态类和静态成员
  5.C++的友元函数和友元类
3.模板编程和STL
  1.C++模板编程入门
  2.STL的容器类和迭代器
  3.STL的泛型算法
  4.模板特化与类型萃取
  5.STL的其他容器讲解
  6.智能指针与STL查漏补缺
4.杂项
  1.c++各种流操作
  2.依赖,关联,聚合,组合,继承
  3.一些技巧性的代码设计
  
  


1.C++新增的引用介绍

string foo( ); void bar(string & s); 

(转载,侵删)那么下面的表达式将是非法的: bar(foo( )); bar("hello world");
原因在于 foo( )和"hello world"串都会产生一个临时对象,而在 C++中,这些临时对象都 是 const 类型的。因此上面的表达式就是试图将一个 const 类型的对象转换为非 const 类型, 这是非法的。引用型参数应该在能被定义为 const 的情况下,尽量定义为 const 。

将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生 runtime error!
注意事项:
(1)不能返回局部变量的引用。这条可以参照 Effective C++[1]的 Item 31。主要原因是 局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。这条可以参照 Effective C++[1]的 Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由 new 分配)就无法释放,造成 memory leak。
(3)可以返回类成员的引用,但最好是 const。这条原则可以参照 Effective C++[1]的 Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的作用: 流操作符<>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷 贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此返回一个流对象引用是唯一选择。赋值操作符=也需要返回引用。

#include <iostream>
using namespace std;
int &put(int n);
int vals[10];
int error=-1;
int main(void)
{
put(0)=10; //以 put(0)函数值作为左值,等价于 vals[0]=10;
put(9)=20; //以 put(9)函数值作为左值,等价于 vals[9]=20;
cout<<vals[0];
cout<<vals[9];
return 0;
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}

2.引用的本质剖析

2.1、引用可以加const修饰
(1)const int &b = a; 表示b是a的const别名,无法通过b修改a了
(2)主要用在函数形参中,告诉大家该函数内部不会修改实参的值。用在某些时候我们有一个非const类型的变量,但是我们在某个函数调用的过程中,不希望变量的值在函数内部被修改,这时候就可以用const引用来传参。
2.2、引用和sizeof运算符
(1)sizeof引用得到的不是引用本身的大小,而是引用指向的目标变量的大小
(2)在struct或class中定义一个引用,再sizeof整个struct或class就会不一样
2.3、引用的本质是const指针
(1)int &b = a; 类似于 int * const b = &a;
(2)C++标准并没有规定引用是否占用内存空间,但是大多数编译器都把引用实现为const指针,所以大部分编译器中引用也是要占内存空间的
(3)引用是天然const的,所以定义时必须初始化指向变量,否则就没意义了
(4)引用本质是指针,是地址,所以才能实现传址调用的效果
总结:引用就是指针在定义时增加了把指针变量本身const化

3.C++的enum枚举

3.1、C++继承C的枚举用法
(1)典型枚举类型定义,枚举变量定义和使用
(2)枚举类型中的枚举值常量不能和其他外部常量名称冲突:举例1宏定义,举例2另一个枚举
3.2、C++11中扩展的枚举
注意编译时:g++ emup.cpp -std=c++11
(1)enum class enumType:valueType{one=xx, two, three};
例子: enum class day:unsigned int{MON = 44, THU, WEN};
简化写法1 enum class day{MON, THU, WEN};
简化写法2 enum day{MON, THU, WEN};
这样在函数中想使用枚举中的元素就需要加上命名空间:day d1; d1 = day::MON;
(2)解决2个枚举中的重名问题,但是宏定义仍然不能重名
3.3、关于枚举的几个小细节
(1)枚举类型和值类型的互相转换,枚举类型是否可以++,可以使用运算符
(2)枚举类型的前置声明
(3)枚举类型超出范围访问是否会编译时或运行时报错(和数组下标越界一样,编译器都不管)
(4)如果不给枚举中的元素赋值,那么第一个元素就是0,后面依次为1、2……。如果给其中一个元素赋值,那么其后面的元素一次加一。初始化时可以赋负数, 以后的标识符仍依次加1。
(5)c++中可以enum day d1,也可以简写:day d1

4.C++的共用体union

7.1、C语言中union回顾
(1)union翻译成共用体更合适,而不是联合、联合体
(2)union中所有成员是多选一的关系,这是union和struct的最大差别
(3)union的典型用法是测试大小端,面试笔试常考,必须掌握

4.2、C++中union和C中不同
(1)C++中union类型定义后使用时可以省去union(和上节enum时一样)
(2)C++中union里成员除了普通的,还可以是对象,但是对象不能包含自定义构造函数、析构函数,简单说就是不能太复杂
(3)C++中经常用到匿名union,一般是内置在class内部做成员变量
4.3、总结
(1)union在C++中没有突出变化,主要还是沿用C中使用

union myu m1;		// C中定义了一个myu类型的变量
myu m1;				// C++中定义了一个myu类型的变量
union 
{
	char *p1;
	int *p2;
}m1;				// 直接定义了union变量m1

5.inline关键字

5.1、C中inline使用关键点强调
(1)inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”,所以关键字 inline 必须与函数定义体放在一起,而不是和声明放在一起
(2)如果希望在多个c文件中使用,则inline函数应该定义在h文件中(不需要额外声明);如果只在一个c文件中使用,则inline函数可以定义在c文件或h文件中(若定义在c文件时可以声明到h文件中去,声明时可以不加inline)
(3)inline函数在项目中可以多次定义,只要函数体完全相同且在一个c文件范围只定义一次
(4)inline只是一种对编译器的建议而不是强制,所以inline函数不一定真被inline
(5)递归函数不应该被声明为inline,超过一定长度(通常是10行)的函数不应该被inline,内含循环的函数不建议被inline
5.2、C++中inline新增的特性
(1)定义在类声明之中的成员函数将自动地成为内联函数
(2)如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。值得注意的是:如果在类体外定义inline函数,则心须将类定义和成员函数的定义都放在同一个头文件中,否则编译时无法进行置换。

6.C++11引入的nullptr

6.1、C语言中的NULL
(1)NULL用来标记野指针
(2)NULL在C和C++中的定义为什么不同?因为C++不允许void *隐式转为int *等类型
(3)C++中也可以继续用NULL,但是因为函数重载的引入,NULL传参会带来歧义
6.2、nullptr如何工作
(1)nullptr传参,表示真正的空指针
(2)nullptr的本质

const class nullptr_t{
public:
	template<class T> inline operator T*()const {return 0;}
	template<class C, class T> inline operator T C::*() const {return 0;}
private:
	void operator&() const;
} nullptr={};

(1)C++11开始可用,注意版本要求
(2)实践中在判断野指针时很多人还是喜欢if (!p)这样···
(3)nullptr无法解决char *pint *p这样的传参重载问题,所以还是有点不完美
(4)nullptr不属于任何一种对象指针,但是却可以表示任何类型的空指针
(5)三个函数名相同的函数,参数不一样

void func(char *p)		void func(int a)		void func(int *p)

NULL在指针和非指针参数中都会引起歧义,所以NULL即表示0也表示零指针。
而nullptr在char*int*参数函数中会引起歧义,因为nullptr是void*

7.使用静态断言

7.1、C中的断言assert C++11
(1)直接参考:https://www.cnblogs.com/lvchaoshun/p/7816288.html
(2)C的assert是运行时检测发现错误,而不是编译时
(3)C在编译时错误用#error来输出
7.2、C++静态断言
(1)C++引入static_assert(表达式, “提示字符串”)来实现编译时的静态断言
(2)实例演示
7.3、静态断言主要用途
(1)static_assert主要用于检查模板参数是否符合期望
(2)C++20中引入了concept来进一步更好的实现模板参数的编译时类型匹配检查
例子:

// 前提:我写了一个项目只能运行在32位系统上,我要防止用户在64位系统上运行该程序
	static_assert((sizeof(void *) == 4), "not support non 32bit system");

8.C++内存对齐

8.1、C语言中内存对齐关键点
(1)#pragma :#pragma pack(2) #pragma pack() 往下去对齐
attribute((packed)) attribute((aligned(n))) 往大去对齐
(2)学习内存对齐的正确姿势
11.2、C++中内存对齐新增关键字
(1)alignof (C++11 起)
(2)alignas (C++11 起)
11.4、什么情况下需要人为改变/指定对齐方式:
(1)往大去对齐。有时候会有一些硬件特殊要求,譬如MMU,cache等。用__attribute__((aligned(n)))实测ok,用#pragma实测不ok
(2)往下去对齐。有时候需要节省内存而浪费效率,所以希望忽略内存对齐,紧密排放。

#include <iostream>

using namespace std;

struct __attribute__((aligned(2)))  s1
{
	char a;			// 1	+3		4
	int b;			// 4	+0		4
	double c;		// 8	+0		8
};

struct __attribute__((aligned(32)))  s1
{
	char a;			// 1	+3		4
	int b;			// 4	+0		4
	double c;		// 8	+0		8
};

struct __attribute__(packed) s1		//实验表明,packed是以最小对齐方式对齐,也就是取消内存对齐
{
	char a;			// 1	+3		4
	int b;			// 4	+0		4
	double c;		// 8	+0		8
};

#pragma pack(2)
struct s1
{
	char a;			// 1	+3		4
	int b;			// 4	+0		4
	double c;		// 8	+0		8
};
#pragma pack()

struct alignas(2)  s1	//C++11新增,和__attribute__((aligned(n)))类似
{
	char a;			// 1	+3		4
	int b;			// 4	+0		4
	double c;		// 8	+0		8
};

int main(void)
{
	// alignof有点像sizeof,用来测一个类型或变量的对齐规则
	cout << "alignof xxx  = " << alignof(s1) << endl;
	cout << "sizeof xxx  = " << sizeof(s1) << endl;
	return 0;
}

9. typeid

9.2、typeid
(1)typeid是一个运算符,类似于sizeof
(2)typeid定义在头文件typeinfo中,必须包含该头文件<typeinfo>
(3)typeid用来返回一个变量(表达式)(对象)的类型
9.3、typeid的深层次说明
(1)一个表达式的类型分静态类型和动态类型,分别对应编译期和运行时类型决策系统
(2)typeid可用来返回静态类型,也可用来返回动态类型
(3)typeid是C++语言本身的特性,由编译器和库函数共同支撑
(4)typeid真正大用在引入class和继承后,并结合指针和引用后才能显现出来
(5) typeid(a).name()用法

10.C++的4种cast转换

10.1、static_cast
(1)源生类型之间的隐式类型转换,可以用static_cast来明确告知编译器,避免警告,转换后可能丢失精度,正确性需要程序员自己保证
(2)用来将void *p转为具体的指针类型,取回原有的指针类型
(3)用于类层次结构中父类和子类之间指针和引用的转换。其中上行转换时安全的,而下行转换时不安全的。
(4)总结:static_cast<>()是编译时静态类型检查,使用static_cast可以尽量发挥编译器的静态类型检查功能,但是并不能保证代码一定“正确”(譬如可能会丢失精度导致错误,可能经过void *之后导致指针类型错误,可能下行转换导致访问错误。)
(5)评价:static_cast必须会用,见了必须认识,能理解使用static_cast的意义,但是实际上只能解决很初级的编程问题,属于初级语法特性。
10.2、reintepret_cast
(1)用于明确告知编译器该类型转换在编译时放行,正确性由程序员自己负责
(2)reintepret_cast转换前后对象的二进制未发生任何变化,只是对这些二进制位的编译器类型标识发生了变化,或者说是编译器看待这些二进制位的结论不同了
(3)reintepret_cast一般用于将指针转成int或者回转,将A类型指针转为B类型指针等
(4)reintepret_cast其实就是让C++在本次转换中放弃严苛的编译器类型检查
10.3、const_cast
常用,因为不能把一个const变量直接赋给一个非const变量,必须要转换
(1)用来修改类型的const或volatile属性,网上说是去除const、volatile限定,大部分用法是去除属性的,如果要加上属性:const int* k = const_case<const int*>(j)而这种用法很少,因为可以直接const int* k = j
(2)格式为:const_cast<type_id> (expression)
(3)思考:const_cast为什么能修改const为非const?
(4) type_id变量类型只能是指针或者引用,让其指向const变量的地址,如下代码

const int a =4;
int *p = &a;

在c中可以编译,但是在c++中不可以编译,必须加关键字const_cast

int *p = const_cast<int *>(&a);	
*p = 6;

但是这样打印出来的a和*p是不一样的值,但是&a和p明明是指向同一个地址的,这是c++搞的事情,编译器对常量优化了,在输出时直接用值替换了,这让const变量真的不可修改,避免编程混乱引起歧义。在const前面加个volatile试试,发现成功修改a的值。以下代码效果相同:

int *p = (int *)&a;		// 老式转换可以,但是不推荐
int *p = const_cast<int *>(&a);		// 新式写法,推荐

10.4、dynamic_cast(比较繁琐,而且牵涉到的内容特别多,关于RTTI与dynamic_cast可看 文章
(1)只用在父子class的指针和引用访问时的转换中,尤其是下行转换时
(2)属于一种运行时转换机制,运行时才能知道转换结果是NULL还是有效对象
(3)运行时确定对象类型RTTI(run time type indentification)是一种需求,C++有一套机制来实现
dynamic_cast运算符它涉及到编译器的属性设置,而且牵扯到的面向对象的多态性跟程序运行时的状态也有关系,所以不能完全的使用传统的转换方式来替代。但是也因此它是最常用,最不可缺少的一个运算符 [1] 。
与static_cast一样,dynamic_cast的转换也需要目标类型和源对象有一定的关系:继承关系。 更准确的说,dynamic_cast是用来检查两者是否有继承关系。因此该运算符实际上只接受基于类对象的指针和引用的类转换。从这个方面来看,似乎dynamic_cast又和reinterpret_cast是一致的,但实际上,它们还是存在着很大的差别。
用法:dynamic_cast (expression)
该运算符把expression转换成type-id类型的对象。Type-id 必须是类的指针、类的引用或者void*;
如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
dynamic_cast运算符可以在执行期决定真正的类型。如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果 downcast 不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

class B
{
	public:
	int m_iNum;
	virtual void foo();
};

class D:public B
{
	public:
	char* m_szName[100];
};
 
void func(B* pb)
{
	D* pd1=static_cast<D*>(pb);
	D* pd2=dynamic_cast<D*>(pb);
}

在上面的代码段中,
1 . 如果 pb 指向一个 D 类型的对象,pd1 和 pd2 是一样的,并且对这两个指针执行 D 类型的任何操作都是安全的;
2 . 如果 pb 指向的是一个 B 类型的对象,那么 pd1 将是一个指向该对象的指针,对它进行 D 类型的操作将是不安全的(如访问 m_szName),而 pd2 将是一个空指针。
3 . 另外要注意:B 要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
如果要用继承,那么一定要让析构函数是虚函数;如果一个函数是虚函数,那么在子类中也要是虚函数。(来自百度百科)

#include<stdlib.h> 
#include<iostream>
using namespace std;
 
class Base
{
public:
    virtual void f() {cout << "Base::f" << endl;}
    void f1() {cout << "Base::f1" << endl;}
private:
    double x;
    double y;
};
 
class Derived : public Base
{
public:
    virtual void f() {cout << "Derived::f" << endl;}
    virtual void k() {cout << "Derived::k" << endl;}
private:
    double z;
};
 
class Base1
{
public:
    virtual void g() {cout << "Base1::g" << endl;}
    void g1() {cout << "Base1::g1" << endl;}
};
 
class Derived1 : public Base, public Base1
{
public:
    virtual void f() {cout << "Derived1::f" << endl;}
    virtual void h() {cout << "Derived1::h" << endl;}
};
 
void Test1()
{
    //对于单继承,
    //如果pD真的指向Derived,用dynamic_cast和static_cast效果相同
    Base* pD = new Derived;
     
    Derived* pD1 = dynamic_cast<Derived*>(pD);
    pD1->f();
    pD1->k();
    pD1->f1();
     
    Derived* pD2 = static_cast<Derived*>(pD);
    pD2->f();
    pD2->k();
    pD2->f1();
     
    //但是如果pB不是真的指向Derived,则用dynamic_cast则返回NULL,能够更早的禁止error的发生,
    //如果用static_cast虽然返回的不为NULL,但是运行时可能抛出exception。
    /**/////Errorcode
    //Base* pB = new Base();
    //Derived* pD3 = static_cast<Derived*>(pB);
    //pD3->f();
    //pD3->k();
    //pD3->f1();
    //Derived*pD4 = dynamic_cast<Derived*>(pB);
    //pD4->f();
    //pD4->k();
    //pD4->f1();
}
 
void Test2()
{
    //对于多重继承,
    //如果pD真的指向的是Derived1,使用dynamic_cast和static_cast都可以转化为Derived1,
    //但是如果要转化为Base的兄弟类Base1,必须使用dynamic_cast,使用static_cast不能编译。
    Base* pD = new Derived1;
    Derived1* pD1 = dynamic_cast<Derived1*>(pD);
    pD1->f();
    pD1->h();
    pD1->f1();
     
    Derived1* pD2 = static_cast<Derived1*>(pD);
    pD2->f();
    pD2->h();
    pD2->f1();
     
    Base1* pB1 = dynamic_cast<Base1*>(pD);
    pB1->g();
     
    /**/errorcannotcompiler
    //Base1* pB2 = static_cast<Base1*>(pD);
    //pB2->g();
    //当然对于pB不是真的指向Derived1,想要转化为Derived1或Base的兄弟类Base1,情况与Test1中的error情况相同。
}
 
int main(void)
{
    Test1();
    Test2();
    return 0 ;
}

11.C++的自动类型推导

11.1、auto关键字
(1)auto在C中修饰局部变量,可以省略,完全无用。C++中的auto完全是一个新关键字
(2)auto要求至少不低于C++11标准来支撑
(3)auto在编译器由编译器帮我们自动推导出变量(对象)类型,所以定义时必须初始化
(4)auto可以一次定义多个同类型的变量,但是不能一次定义多个类型不同的变量,这是auto的类型推导机制决定的。
11.2、decltype关键字
(1)C++11新增关键字
(2)decltype可以让编译器推导目标表达式的类型作为一种类型符使用
(3)decltype(表达式)作为类型定义变量不要求初始化
11.3、auto和decltype的对比
(1)auto忽略顶层const,而decltype则保留const
(2)auto作为类型占用符,而decltype用法类似于sizeof运算符
(3)对引用操作,auto推断出原有类型,decltype推断出引用

1.	const int ci = 42, &cj = ci;
2.	decltype(cj) y = x;   // y 类型为const int&
3.	auto h = cj;          // h 类型为int

(4)对解引用操作,auto推断出原有类型,decltype推断出引用
(5)auto推断时会实际执行,decltype不会执行,只做分析。

如果给这个变量加上一个或多层括号,那么编译器会把这个变量当作一个表达式看待,变量是一个可以作为左值的特殊表达式,所以这样的decltype就会返回引用类型。

decltype((i))		//int&型
1.	auto &g = ci; //g是一个整数常量引用,绑定到ci。
2.	auto &h = 42; // 错误:非常量引用的初始值必须为左值。
3.	const auto &j = 42; //正确:常量引用可以绑定到字面值。

(6)

int func(void)
{}
auto a = func();
	decltype(func) b;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;

以上代码执行结果:

i	//auto返回类型为int类型,而且发现auto的类型和函数返回的类型相同,如果函数返回char,那么auto也是char类型的。
FivE	//decltype返回类型为函数类型,    iv分别代表int和void

b打印出来是1,而且不能通过b;或者b();来执行此函数。

12.C++类与面向对象

struct和class

13.C++中static和this关键字

13.1、static在C中的用法
(1)静态全局变量和函数,限制链接属性。C++中建议优先使用命名空间机制替代
(2)静态局部变量,更改地址域和生命周期。C++中继续沿用。
13.2、static在C++中新增用法
(1)用在class中,有静态数据成员和静态成员函数
(2)简单理解:静态成员和方法是属于class的,而非静态是属于对象的
(3)静态类往往用在单例模式中,实际上和面向对象的思想有所差异
(4)要真正理解静态类,得先学习面向对象和普通非静态类后才可以
13.3、this关键字
(1)本质是个指针,指向当前对象
(2)this的主要作用是让我们在未定义对象前 可以在方法中调用对象里的成员

14.C++面向对象的其他关键字

14.1、面向对象允许类的继承机制
(1)C++中用:来表示继承关系,有些编程语言有extends关键字表示继承关系
(2)virtual修饰class的成员函数为虚函数,一般在基类中,只有接口声明没有实体定义
(3)基类的virtual成员可以在派生类中override重写,以实现面向对象的多态特性
(4)注意区分重写override重载overload
(5)override关键字是C++11引入,用来在派生类中成员函数声明时明确表明需要派生类去重写的那些成员方法,这样如果程序员在成员方法实体定义中做的不对编译器可以报错提醒
14.2、继承的终止final
(1)一个class不希望被继承(不想做父类),可以定义时用final修饰
(2)一个成员方法不希望被子类override,可以声明时用final修饰
(3)final是C++11引入的
(4)很多其他面向对象语言如java中也有final关键字,也是这个作用
14.3、using关键字
(1)用法1就是using namespace std;这种
(2)用法2与class的继承和访问权限限制有关,属于一个声明,能够让private继承的子类去声明并访问父类中本来无权限访问的成员
14.4、operator
(1)用于运算符重载,也就是为一个class重定义某种运算符
14.5、friend
(1)让不属于一个class的外部函数也能访问class内受保护的成员变量
(2)实际上是对面向对象的一种扩展或者说破坏,在面向对象深入理解之后再来学习更好
14.6、explicit
(1)本意为显式的,对应implicit隐式的
(2)用来修饰只有一个参数的构造函数,以阻值构造函数不合时宜的类型转换
(3)很简单一个特性,第2部分再详解

15.C++的const关键字

15.1、C语言中const用法回顾
(1)const变量,比宏定义的优势是带数据类型,可以让编译器帮我们做类型检查
(2)const数组,和常量变量类似
(3)const指针,三种情况:const int *p, int * const p, const int *const p;
15.2、C++中const新增用法
(1)const引用,主要用于函数传参,限制函数内部对实参进行修改
(2)const成员函数,使用时加在函数名后面:int func(int a) const;限制函数内部对类的成员变量的修改

#include <iostream>
using namespace std;
/*
// 比较传入的数,大于100就返回0,小于等于100就返回-1
int func(int a)
{
	if (a > 100)
		return 0;
	else
		return -1;
}

// 要求传地址,形参定义时加const是为了告知所有人(负责实现函数的,调用该函数的,编译器),func1函数内不会修改传参pa所指向的实际值(实参)   
// 调用时,int i;		func1(const_cast<const int *>(&i));
int func1(const int *pa)
{
	if (*pa > 100)
		return 0;
	else
		return -1;
}

// C++中更倾向于使用引用而不是指针
// 调用时,int i; 		func2(i);
int func2(const int &a)
{
	if (a > 100)
		return 0;
	else
		return -1;
}
*/

struct A
{
public:
	int i;	
	int func6(void) const ;
/*
	int i;
	int j;
	
	int func8(int &a);		// 这样写,隐含意思就是func8内部很有可能会修改传参a的值
	int func9(const int &a);		// 这样写,隐含意思就是func9内部不会改变a的值
	int func10(int a) const;		// const成员,明确告知func10内部不会修改class A
									// 的成员变量的值
*/	
};
int A::func6(void) const
{
//	this->i = 5;
	cout << "A::func6, i = " << this->i << endl;
}
int main(void)
{
	A a;
	a.i = 1;
	a.func6();
	cout << "a.i = " << a.i << endl;
	return 0;
}

16.const有关的其他几个关键字

16.1、mutable
(1)mutable用来突破const成员函数的限制,让其可以修改特定的成员变量
(2)案例参考:https://www.cnblogs.com/yongdaimi/p/9565996.html
16.2、constexpr
(1)用法如下:

constexpr int multiply (int x, int y)
{
    return x * y;
}
const int val = multiply( 10, 10 );		**// 将在编译时计算**
const int val = 100;

(2)本质上是让程序利用编译时的计算能力,增加运行时效率
(3)由C++11引入,但是实际有一些编译器并不支持,需实际测试
16.3、C++20新引入的2个
(1)constinit https://zh.cppreference.com/w/cpp/language/constinit
(2)consteval https://zh.cppreference.com/w/cpp/language/consteval

17.模板编程的几个关键字

17.1、模(mu)板编程初体验
(1)template和typename
自定义一个抽象类型,譬如命名为X,编程的时候用X编程,X的具体类型在调用函数时由实参的类型来确定

template <typename T>
T add(T a, T b)
{
	return (a + b);
}

(2)模板实际上是一种抽象,C++的高级编程特性就是不断向抽象化发展
17.2、export
(1)用来在cpp文件中定义一个模板类或模板函数,而它的声明在对应的h文件中
(2)export专用于模板,类似于extern之于简单类型
(3)实际很多环境不支持,暂不必细究
17.3、requires
(1)C++20引入,用于表示模板的参数约束

18.C++的异常处理机制

18.1、何为异常处理
(1)异常exception,即运行时错误
(2)C中没有异常机制,所以运行时遇到错误只能终止程序
(3)C++中新增了异常处理机制,允许程序在运行时拦截错误并处理,这样程序就不用终止
(4)异常机制的一个典型案例就是:由用户输入2个数字然后相除中的除0异常
18.2、异常处理编程实践

#include <iostream>
using namespace std;
int main(void)
{
	// 让用户输入2个数,然后程序返回他的相除
	cout << "please input 2 numbers" << endl;
	int m, n;
	cin >> m >> n;
	
	// C++中用异常处理机制来处理
	try
	{
		// try括号里的代码就是有可能触发异常的代码
		if (n == 0)
			throw ('A');
		cout << "m / n = " << m/n << endl;
	}
	catch (int e)		// catch的()里写上要抓取的异常类型
	{
		cout << "catch int e" << endl;
	}
	catch (double e)		// catch的()里写上要抓取的异常类型
	{
		cout << "catch double e" << endl;
	}	
/*	
	// C中我们这样处理
	if (n == 0)
	{
		cout << "0 not good" << endl;
		return -1;
	}
	else
	{
		cout << "m / n = " << m/n << endl;
	}
*/		
	cout << "---other code---" << endl;	
	return 0;
}

void func(void) throw(A, B, C);		// 这种声明就是告诉调用者func有可能抛出3种异常

(1)try, catch, throw
(2)异常处理机制为什么优于出错条件判断:https://www.cnblogs.com/wkfvawl/p/10816156.html

18.3、异常和函数
(2)throw一个异常后如果没有catch会层层向外传递直到被catch为止
(3)函数可以用throw列表来标识自己会抛出的异常
18.4、标准库中的exception类
(1)标准库中定义的异常类及其派生类,很多内置代码的错误会抛出这些异常
(2)譬如bad_typeid,使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常
(3)譬如bad_cast,用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常
18.5、noexcept关键字
(1)throw(int, double, A, B, C)表示函数可能会抛出这5种类型的exception
(2)throw() 表示函数不会抛出任何异常
(3)C++11中引入noexcept关键字替代throw()表示函数不会抛出任何异常
noexcept(bool)
(4)没有throw列表的函数,表示函数可能会抛出任意类型的异常

19.剩余关键字和总结

19.1、剩余一些关键字
(1)线程相关:thread_local (C++11 起)
(2)import和module (C++20)
(3)协程相关:
co_await (C++20 起)
co_return (C++20 起)
co_yield (C++20 起)
(4)并发相关:synchronized (TM TS)
(5)反射相关:reflexpr (反射 TS)
(6)其他:
transaction_safe (TM TS)
transaction_safe_dynamic (TM TS)
atomic_cancel (TM TS)
atomic_commit (TM TS)
atomic_noexcept (TM TS)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值