C++关键字说明

参考资料

cpp关键字图示

1)auto

cpp11的 auto 表示变量的自动类型推断。即在声明变量的时候,根据变量初始值的类型自动为此变量选择匹配的类型。

auto x = 3; // x 为 int 类型
cout << typeid(x).name() << endl;auto 

变量必须在定义时初始化,这类似于const关键字。

2)bool、true、false

bool 类型是cpp 中的基本数据结构。bool 类型只有两个取值,true 和 false。true 表示“真”,false 表示“假”。
bool 类型常用于条件判断、开关变量的值或函数返回值。

3)char、wchar_t

char 类型表示单个字符。char 类型的数据需要用单引号括起来:
char letter =‘A’;
wchar_t 是宽字符类型,每个 wchar_t 类型占2个字节,16位宽。汉字的表示就需要用到 wchar_t。
字符与整数密切相关,它们在内部其实是被存储为整数。每个可打印的字符以及许多不可打印的字符都被分配一个唯一的数字。用于编码字符的最常见方法是 ASCII(美国信息交换标准代码的首字母简写)。
备注:UTF-8编码方式,中文占3个字符,中文标点也占3个字符。UTF-8存储中文时占2-4个字节。utf-8是变长的、不定长的,ucs-4范围是1~6字节。 决定一个utf8字长度得看它首个字符,根据左侧位1的个数来决定占用了几个字节。

4)int、short、long

5)float、double、long double

6)signed和unsigned

区别在于二进制数据时,第一位是否为符号位

7)enum枚举类型

enum 表示枚举类型,可以给出一系列固定值,实质上是 int 类型

enum color {
    RED = 0,
    GREEN = 1,
    BLUE = 2 
};

8)union联合体类型

union 是联合体类型,通过共享内存,一个union可以有多个数据成员。但在任意时刻,联合中只能有一个数据成员可以有值。例如

union price {
    char x
    int y;
    double z; 
};

9)struct和class

class是一般的类类型,struct在cpp中是特殊的类类型,声明中默认的访问权限与class不同,struct是public,class是private。

结构体是一种特殊形态的类,它和类一样,可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限,可以继承,支持包含多态等,二者定义的语法形式也几乎一样。结构体和类的唯一区别在于,结构体和类具有不同的默认访问控制属性:在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型(private) ;在结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为公有类型。

结构体可以有函数成员(包含构造函数和析构函数),但实质上是函数指针。C语言没有权限控制的说法,C语言的结构体自然不能对成员进行权限控制。

#include <stdio.h>
typedef struct CStructure
{
int (*memberFunction)(); //结构体里的函数成员,实质上是函数指针
}CStructure;
int globalFunction()
{
printf("Member function of a struct in C\n");
return 0;
}
int main()
{
CStructure obj; //创建结构体对象
obj.memberFunction=globalFunction;//为函数指针赋值
obj.memberFunction();//使用函数指针
return 0;
}

10)sizeof运算符用于获取数据类型占用的字节数

sizeof 运算法用于获取数据类型占用的字节数。

cpp中有5种不能重载的运算符:两个是类的成员引用符号(.和->),一个是类空间引用符号(:😃,一个是唯一的三元运算符(?😃,还有一个就是sizeof运算符

11)typeid运算符可以输出变量的类型

typeid运算符可以输出变量的类型。
程序示例1
程序示例2

12)typedef

typedef 可以为现有数据类型创建一个别名,便于程序的阅读和编写。
补充

typedef和define都是替一个对象取一个别名,以此增强程序的可读性,区别如下:
(1)原理不同

#define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。

typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。用typedef定义数组、指针、结构等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。

(2)功能不同

typedef用来定义类型的别名,一是起到类型易于记忆的功能。另一个是定义机器无关的类型。如定义一个REAL的浮点类型,在目标机器上它可以获得最高的精度:typedef long double REAL, 在不支持long double的机器上,看起来是这样的,typedef double REAL,在不支持double的机器上,是这样的,typedef float REAL

#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

(3)作用域不同

#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域

(4)对指针的操作不同

例1:
#define INTPTR1 int*     //新的别名放在中间,注意:#define N 5,别名N在中间
typedef int* INTPTR2; //新的别名在后面
INTPTR1 p1, p2;
INTPTR2 p3, p4;
含义分别为,
声明一个指针变量p1和一个整型变量p2 //因为#define知识简单的字符替换
声明两个指针变量p3、p4
例2:
#define INTPTR1 int*
typedef int* INTPTR2;
int a = 1;
int b = 2;
int c = 3;
const INTPTR1 p1 = &a;//等同于const int* p1,p1是常量指针
const INTPTR2 p2 = &b;//等同于const int* p2,p2是常量指针
INTPTR2 const p3 = &c;//等同于int* const p3,p3是指针常量
//一、关于“指针常量“和“常量指针”的一些思考:
主要是看这个表述最后的那个名词:是常量还是指针。那么它的本质就是对应的常量还是指针。
//1、指针常量:本质是常量,但是常量里面装的是指针。
表达式为(先*const):int* const p;
所以,指针指向的地址不能变,但是地址中存储的值可以变化。
int a=1,b=2;
int* const p=&a;
cout<<*p<<endl; //值为1
*p=7; //正确,指针常量允许修改值。
*p=&b; //错误!指针常量不允许修改指向的地址

//2、常量指针:本质是指针,但是指针指向常量
表达式为(先const*):const int* q;或者 int const* q; 
注意: const* int q;会报错。
所以,指针指向的地址可以变,但是指针所指向的值不允许变化。
int c=1,d=2;
int const *q=&c;  // 或者 const int* q=&c;
cout<<*q<<endl; //值为1
*q=7; //错误!常量指针不允许修改值。
q=&d; //正确,常量指针允许修改指向的地址

因为引用 是指针常量嘛,所以引用本身是一个常量,但是里面装的是指针。即:引用的指针不能变,但是指向的内容可以变化。在定义引用的同时,必须初始化。

  • 引用除了给变量赋予新的名称,还可以用于函数形参(常见于类的拷贝构造)。
  • 引用可以用作函数返回值。注意,不要返回局部变量。还有函数调用作左值的使用。
  • 常量引用,形如:const int& v;

小技巧

  • 指针常量/常量指针,就看这个词的最后两个字是什么,它的本质就是什么。
  • 指针常量:是一个常量,常量里面装的是指针,所以指针的指向内存不能改变;
  • 常量指针:是一个指针,指针指向内存中存放的数据是称量,所以指针的指向内存可以变,但是存放的数据可以变。

13)static

用于声明静态变量或类的静态函数。静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为 0,使用时可改变其值。
cpp 类的成员变量被声明为 static(称为静态成员变量),意味着它被该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见;而类的静态成员函数也只能访问静态成员(变量或函数)。
static和extern

对于全局变量和函数而言,两者功能相反,extern想让程序使用其他文件的函数或全局变量;static是将函数和全局变量限制在本文件当中。

对于局部变量,static是将变量的创建空间从栈区更改到了静态数据区。
class类中的static成员函数/变量 和 const成员函数/变量

0、前言

为什么cpp中会出现/保留static关键字和const关键字呢?
参考链接:https://zhuanlan.zhihu.com/p/141113043

static有两个功能,对于全局变量和函数,是限制其作用域;对于局部变量,是将其创建空间从栈区更改到静态存储区,以保证在函数调用结束时,变量值不会被释放。

在cpp类的多个对象中,如果我们想要一个变量值,可以让所有对象共享,常规的做法是使用全局变量。但是全局变量的使用破坏了类的封装性,而类的静态变量,既可以让类内对象共享,也可以对类外对象隐藏。

cpp中为什么会引入const

cpp有一个类型严格的编译系统,这使得cpp程序的错误在编译阶段即可发现许多,从而使得出错率大为减少,因此,也成为了cpp与c相比,有着突出优点的一个方面。

c中很常用的预处理命令#define 变量名 变量值,可以很方便地进行值替代。这种值代替至少有三个优势:

一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例:
  #define user_num_max 107 这样就避免了直接使用107带来的困惑。
  二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,进改动此处即可,
  三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。

预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受cpp严格类型检查的好处,从而可能成为引发一系列错误的隐患。

结论:

const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

现在它的形式变为:const typename 变量名=变量值为什么const能很好的替代预定义命令?

1. 首先,以const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。
2. 第二,很明显,它也同样可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
3. 第三,cpp的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。这里,我要提一下,为什么说这一点是也是它能取代预定义语句的基础,这是因为,编译器不会去读存储的内容,如果编译器为const分配了存储空间,它就不能够成为一个编译期间的常量了。
4. 最后,const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义语句的隐患。

const 使用场景分析

  1. const用于修饰指针的两种情况
int const *a;  //a可变,*a不可变 [常量指针,本质为指针,指针指向的内存空间存放的是常量。所以,值不能变,指针能变。即*a不能变,a能变]
int *const a;  //a不可变,*a可变 [指针常量,本质为常量,常量里存放的是指针。所以指针指向不能变,值能变,例如cpp的引用。即a不能变,*a能变]

分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个类型修饰符,所以,int const 限定*a,不限定a。int *const 限定a,不限定*a。
2. const 限定函数的传递值参数
分析:上述写法限定参数在函数体中不可被改变。由值传递的特点可知,var在函数体中的改变不会影响到函数外部。所以,此限定与函数的使用者无关,仅与函数的编写者有关。
3. const限定函数的返回值

const int fun1(); 
const myclass fun2();

分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如fun1),已经是一个数值,当然不可被赋值更新,所以,此时const无意义,最好去掉,以免困惑。当函数返回自定义的类型时(如fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。

一般而言,当返回值类型是指针或者class类,或者struct结构体时,返回值用const限制才比较合适。当返回值是基本数据类型时,用const并无意义。
4. 传递与返回指针

此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。
5. const限定类的成员函数

class classname {
 public:
  int fun() const; //常成员函数
 .....
}

注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。

获得能力:可以操作常量对象。
失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。

const使用总结

  1. 函数返回值为const时,返回的东西赋给一个类型相同的标示后其不能为左值;
  2. 用const定义的int可用来开辟数组,但const定义的常量数组中的元素,不能用来定义数组。
  3. const int *i; int const *i; int * const i; 前两个功能相同,说明i所指向的值不变;最后一个说明指针指向的地址不变,但值可以变。
  4. 类中的const成员函数,定义为在原型后加const。常量函数不能修改类中的任何属性。但有两种方法可以修改。
  1. {(yourclass *)this->member = values;}
  2. 将一个成员定义成mutable即可被常量函数修改。
  1. 类中的常量const 类型的,不能在类中被用来定义数组。而enum {ONE=100; TWO=2};定义的ONE、TWO 却可以。通常的enum定义的值分配问题:enum A{ L=9, Z};此时Z的值为10。

一、静态数据成员的特点

  • 静态成员不属于某一个对象,而是属于整个类(定义在数据段)
  • 静态成员供所有对象共享,每个对象可以调用且修改。一个对象修改,其他对象也跟着变化
  • 可以直接通过类名直接访问
  • 注意! 静态数据成员,类内定义,类外初始化

二、类的静态成员函数

  • 类的静态成员函数不属于某一个对象,属于整个类,所以不存在this指针
  • 因为没有this指针,所以不能调用普通成员函数和变量
  • 静态成员函数不能用const修饰
  • 静态成员函数只能访问类的静态成员,不能访问普通成员。因为类的静态成员是属于整个类的,在类定义好的时候就已经在内存开辟了空间,而普通成员是在对象生成的时候才在内存开辟空间,如果使用静态成员函数去访问普通的类会出错
  • 普通成员函数可以调用静态成员或者非静态成员

三、类的常量成员const
常量数据成员特点:

  • 必须在构造函数那里进行列表初始化,不可以在构造函数内部初始化
  • 初始化以后不可以再修改

四、常量成员函数const

  • 常量成员函数内不允许对类的成员变量进行修改,也不允许对函数的const参数进行修改(防止对成员变量误操作)

五、比较compare:静态和常量

  • 静态成员变量类内定义,类外初始化。常量成员必须在构造函数的初始化列表中初始化,不能在函数体中初始化;
  • 普通对象能调用静态成员函数,能调用常量成员函数。静态对象只能调用静态成员函数,不能访问普通成员变量和函数*(因为类的静态成员是属于整个类的,在类定义好时就已经在内存中开辟了空间,而普通成员是在对象生成的时候才在内存中开辟空间,如果使用静态成员函数访问普通成员变量/函数就会出错)*。常对象只能访问常成员函数。常成员函数只能调用常成员变量(防止这个函数修改普通变量的值),不过普通变量可用mutable关键字修饰,这样,常成员函数就可以了调用和修改它了。
  • 需要注意的是,其实静态成员函数可以访问为非静态成员变量,只是十分复杂,故不建议采用。之前说到,静态成员函数不能访问普通成员变量,是因为没有this指针,那么如果静态函数的形参就包含该类,那就可以访问该类的普通成员变量了。如类A中有static void f(A a); static int a,int b,void A::f(A a) {cout<<a;//正确,cout<<b;//错误;cout<<a.b/正确}
  • 建议规范调用静态成员函数和变量,即使用类名而非对象名来调用静态对象。如A::a和A::func(),而不是A obj;obj.a和obj.func()。

14)public、protected、private

权限修饰符。

15)virtual

用于声明虚基类、虚函数。虚函数=0时,则为纯虚函数,纯虚函数所在的类称为抽象类。

16)override、final

override 用于表示当前函数重写了基类的虚函数。
final 用于禁止类继承、禁止重载虚函数。

17)operator

用于重载操作符。如下重载类Person的 == 运算法:
程序示例

18)const、constexpr

const 表示所修饰的对象或变量不能被改变。
constexpr 用于生成常量表达式,常量表达式主要是允许一些计算发生在编译时,而不是运行的时候。

19)using

用于在当前文件引入命名空间,例如:using namespace std;
在子类中,使用 using 声明引入基类成员名称。

20)namespace

cpp标准程序库中的所有标识符都被定义于一个名为 std 的namespace中。
命名空间除了系统定义的名字空间之外,还可以自己定义,定义命名空间用关键字 namespace,使用命名空间时用符号 :: 指定。

21)inline

声明为内联函数,即在编译时将所调用的函数代码直接嵌入到主调函数中。
作用是提高效率,但是程序应尽量短小

22)new、delete 补充

new 用于向内存申请一段新的空间,delete 用于释放申请空间。
用new创建一个int型的空间,并附初始值:int *p=new int(2);delete p
用new创建一个数组的空间,并附初始值:int *p=new int [5](0); delete[] p

23)this

每个类成员函数都隐含了一个this指针,用来指向类本身。
this指针一般可以省略,但在赋值运算符重载的时候要显示使用。静态成员函数没有this指针。

24)nullptr

cpp11新引入的,用来声明一个 空指针,代替NULL。
int* p = nullptr;

25)void

特殊的"空"类型,指定函数无返回值或无参数。

26)friend

用于声明友元关系。
友元可以访问与其有 friend 关系的类中的 private/protected 成员,通过友元直接访问类中的 private/protected 成员的主要目的是提高效率。
友元包括友元函数和友元类。全局函数/成员函数做友元,类做友元。

4.4.1 全局函数做友元
class Building
{
	//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
	friend void goodGay(Building * building);

public:

	Building()
	{
		this->m_SittingRoom = "客厅";
		this->m_BedRoom = "卧室";
	}


public:
	string m_SittingRoom; //客厅

private:
	string m_BedRoom; //卧室
};


void goodGay(Building * building)
{
	cout << "好基友正在访问: " << building->m_SittingRoom << endl;
	cout << "好基友正在访问: " << building->m_BedRoom << endl;
}


void test01()
{
	Building b;
	goodGay(&b);
}

int main(){

	test01();

	system("pause");
	return 0;
}
4.4.2 类做友元
class Building;
class goodGay
{
public:
	goodGay();
	void visit();
private:
	Building *building;
};
class Building
{
	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
	friend class goodGay;
public:
	Building();
public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};
Building::Building()
{
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
	building = new Building;
}
void goodGay::visit()
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
	goodGay gg;
	gg.visit();

}
int main(){

	test01();

	system("pause");
	return 0;
}
4.4.3 成员函数做友元

class Building;
class goodGay
{
public:

	goodGay();
	void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
	void visit2(); 

private:
	Building *building;
};


class Building
{
	//告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
	friend void goodGay::visit();

public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};

Building::Building()
{
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}

goodGay::goodGay()
{
	building = new Building;
}

void goodGay::visit()
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	//cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
	goodGay  gg;
	gg.visit();

}

int main(){
    
	test01();

	system("pause");
	return 0;
}

27)template

模板,cpp中泛型机制的实现。模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。

28)if、else

用于条件语句。

29)for、while、do

用于循环语句。

30)switch、case、default

用于分支语句。switch 表示分支语句的起始,根据 switch 条件跳转到 case 标记或 defalut 标记的分支上。

31)break、continue、goto

break用于跳出for、while循环或switch语句。
continue用于跳到一个循环的起始位置。
goto用于无条件跳转到函数内的标记处,一般情况不建议使用goto。

32)and、or、xor、not、bitand、bitor

and 表示逻辑与 &&;
or 表示逻辑或 ||;
xor 表示逻辑异或 ^;
not 表示逻辑非 !;
bitand 表示按位与 &;
bitor 表示按位或 |。

33)return

return表示从被调函数返回到主调函数继续执行,返回时可带一个返回值。

34)try、catch、throw

用于异常处理。try 指定 try 块的起始,try 块后的 catch 可以捕获异常,异常由 throw 抛出。

35)noexcept

cpp11中,用于声明一个函数不可以抛出任何异常。

36)static_cast、const_cast、dynamic_cast、reinterpret_cast

cpp类型风格来性转换:
static_cast用于静态转换;
const_cast删除const变量的属性,方便赋值;
dynamic_cast用于将一个父类对象的指针转换为子类对象的指针或引用;
reinterpret_cast将一种类型转换为另一种不同的类型。
参考链接:https://www.cnblogs.com/wangchaoguo-li/p/14210679.html

方式使用场景
static_cast基本数据类型之间的转换使用,例如float转int,int转char等;子类对象指针转换成父类对象指针也可以使用static_cast;在有类型指针和void*之间转换使用,不能使用static_cast在有类型指针之间进行类型转换。
dynamic_cast用于将父类的指针或引用转换为子类的指针或引用,此场景下父类必须要有虚函数(只要拥有虚函数就行)
const_cast用于常量指针或引用与非常量指针或引用之间的转换。
reinterpret_cast类似C语言中的强制类型转换,什么都可以转,尽量不要使用此方式。

static_cast

基本数据类型之间的转换使用,例如float转int,int转char等,在有类型指针和void*之间转换使用,子类对象指针转换成父类对象指针也可以使用static_cast(不能使用static_cast在有类型指针之间进行类型转换)。

#include <iostream>

using namespace std;

struct Base {
    virtual void Func() { cout << "Base Func \n"; }
};

struct Derive : public Base {
    void Func() override { cout << "Derive Func \n"; }
};

int main()
{
    float f = 1.23;
    cout << "f " << f << endl;
    int i = static_cast<int>(f);
    cout << "i " << i << endl;

    void *p;
    int *i_p = static_cast<int *>(p);
    void *pi = static_cast<void *>(&f);
    int *pi = static_cast<int *>(&f);  //error invalid static_cast from type ‘float*’ to type ‘int*’

    Derive d;
    d.Func();
    Base *b = static_cast<Base *>(&d);
    b->Func();
    return 0;
}

dynamic_cast

用于将父类的指针或引用转换为子类的指针或引用,此场景下父类必须要有虚函数(只要拥有虚函数就行),因为dynamic_cast是运行时检查,检查需要运行时信息RTTI。

#include <iostream>

using namespace std;

struct Base {
    virtual void Func() { cout << "Base Func \n"; }
};

struct Derive : public Base {
    void Func() override { cout << "Derive Func \n"; }
};

int main() {
    Derive d;
    d.Func();
    Base *b = dynamic_cast<Base *>(&d);
    b->Func();
    Derive *dd = dynamic_cast<Derive *>(b);
    dd->Func();
    return 0;
}

const_cast

用于常量指针或引用与非常量指针或引用之间的转换,只有const_cast才可以对常量进行操作,一般都是用它来去除常量性(去除常量性是危险操作,还是要谨慎操作)。

int main() {
    int data = 10;
    const int *cpi = &data;

    int *pi = const_cast<int *>(cpi);

    const int *cpii = const_cast<const int *>(pi);
    return 0;
}

reinterpret_cast

类似C语言中的强制类型转换,什么都可以转,万不得已不要使用,一般前三种转换方式不能解决问题了使用这种强制类型转换方式。

int main() {
    int data = 10;
    int *pi = &data;

    float *fpi = reinterpret_cast<float *>(pi);

    return 0;
}

37)register

提示编译器尽可能把变量存入到CPU内部寄存器中。

38)explicit

explicit 的作用是禁止单参数构造函数被用于自动类型转换,比较典型的是容器类型。

再次注意,是单参数,形如explicit Fraction(int numerator:m_numerator(numerator){},还有一种情况是多参数,但是后面几个参数都有默认值,只有第一个参数没有默认值:explicit Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}

注意:当类的声明和定义分别在两个文件中时,explicit只能写在在声明.h中,不能写在定义.c中。
参考链接:CSDN
explicit意为清晰地,明确的意思,顾名思义,它的作用就是阻止隐式转换的发生。
例如: cpp中只带有一个参数的构造函数,或者或者除了第一个参数外其余参数都有缺省值的多参构造函数,承担了两个角色:
1.用于构建单参数的类对象.
2.隐含的类型转换操作符.

例如:一个类A的构造函数A(int i)就是,既可以用来作为构造器,又可以实现隐式转换A a=1;因为1可以通过构造函数A(int i)转换为一个类A的对象。(隐含的类型转换操作符)

但有时候,我们并不想让他进行隐式转换,这是cpp的explicit关键字就起作用了。
explicit的三种使用情况:类型转换(operator())单操作数构造函数同时出现类型转换和有参构造函数
再说下面的内容前,需要提及:类型类型转换函数的一般形式:

operator 数据类型() const
{
	//函数实现
}

1.转换函数必须是类的成员函数
2.转换函数不能声明返回类型
3.形参列表必须为空
4.类型转换函数通常应该是const

使用情况1:类型转换

#include<iostream>
using namespace std;
class Fraction{
public:
	Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}
	operator double() const   //类型转换函数
	{
		return (double)m_numerator/m_denominator;
	}
private:
	int m_numerator;
	int m_denominator;
};

int main(void)
{
	Fraction f(3, 5);
	double d = 3.5 + f;
	cout << d << endl;
	return 0;
}

我们设计了一个Fraction类(分数类), 在主函数中定义了一个分数对象f,然后将3.5 + f赋值给double类型变量d, 但是我们发现f并不是一个double类型的变量,因此编译器会从Fraction类中寻找operator double()函数,隐式调用该函数将Fraction类型转换成一个double类型. operator double()就是我们所说的类型转换函数(type conversion function).

注意,此代码中含有隐式类型转换。在计算3.5+f时,类的对象f回调用类型转换函数operator double()来将f转换成double。

使用explicit关键字来避免隐式类型转换,在进行类型转换时,必须显示声明:用static_cast<double>f

#include<iostream>
using namespace std;
class Fraction{
public:
	Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}
	explicit operator double() const    //不能隐式类型转换了
	{
		return (double)m_numerator/m_denominator;
	}
private:
	int m_numerator;
	int m_denominator;
}int main(void)
{
	Fraction f(3, 5);
	double d = 3.5 + static_cast<double>(f);//必须显式类型转换
	cout << d << endl;
	return 0;
}

使用情况2:有参构造函数

对于3.5+f,上面的例子是使用类型转换来实现的。其实,也可以使用重载运算符+的方法来实现,不过,需要写成f+3.5的形式。

//code 1
#include<iostream>
using namespace std;
class Fraction
{
public:
    Fraction(int numerator, int denominator = 1) : m_numerator(numerator), m_denominator(denominator) {}
    double operator+(double a)
    {
        return (double)this->m_numerator /this->m_denominator+a;
    }
private:
    int m_numerator;
    int m_denominator;
};

int main(void)
{
    Fraction f(3, 5);
    double d = f+3.5;
    cout << d << endl;
    system("pause");
    return 0;
}
operator+中隐藏的类型转换//
//code 2
#include<iostream>
using namespace std;
class Fraction
{
 public:
 	Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}
 	double operator+(const Fraction& a)
 	{
 		return (a.m_numerator + this->m_numerator)/(a.m_denominator + this->m_denominator);
 	}
 private:
 	int m_numerator;
	int m_denominator;
 }
 
 int main(void)
{
	Fraction f(3, 5);
	double d = f + 3;
	cout << d << endl;
	return 0;
}

在double d = f + 3 这句话中构造函数就是前面所提到的第二种角色隐含的类型转换操作符.,因为执行到这句话首先会调用+的重载函数,该函数的调用对象默认为左操作数,右操作数为Fraction类型,因此会调用有参构造函数将3转换成Fraction类型,然后将得到的返回值double类型赋值给变量d.

同理如果不想让构造函数进行隐式类型转换,可以在构造函数前面加上explicit关键字,防止进行隐式转换.使用方法如下:

#include<iostream>
using namespace std;
class Fraction
{
 public:
 	explicit Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}
 	double operator+(const Fraction& a)
 	{
 		return (a.m_numerator + this->m_numerator)/(a.m_denominator + this->m_denominator);
 	}
 private:
 	int m_numerator;
	int m_denominator;
 };
 
 int main(void)
{
	Fraction f(3, 5);
	double d = f + 3;//注意,此处代码会报错
	cout << d << endl;
	system("pause");
	return 0;
	//想要代码运行,需要将3类型准换为Fraction类,即使用类的有参构造函数。但是有参构造函数使用explicit修饰,不能隐式类型转换。
}

使用情形3:有参构造和类型转换函数同时出现

#include<iostream>
using namespace std;
class Fraction
{
 public:
 	Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}
 	operator int()  //强制类型转换
 	{
 		return m_numerator/denominator;
 	}
 	int operator+(const Fraction& a)//重载运算符+,以及隐式使用了有参构造函数
 	{
 		return (a.m_numerator + this->m_numerator)/(a.m_denominator + this->m_denominator);
 	}
 private:
 	int m_numerator;
	int m_denominator;
 }

 int main(void)
{
	Fraction f(3, 5);
	int d = f + 3;//上面两种方法都有效,使用哪个?有歧义
	cout << d << endl;
	return 0;
}

这时你会发现会产生一个二义性问题,在执行int d = f + 3的时候到底是该选择类型转换函数,将f转换成int类型再继续运算呢?还是应该将3作为构造函数的参数进行隐式转换,然后再调用+运算符重载函数呢?
解决这个问题的办法就是使用explicit关键字限制,具体方法有两种:
1.在构造函数前面加上explicit关键字, 防止int类型隐式转换成为Fraction类型.
2.在类型转换函数前面加上explicit关键字,这样只有显示调用类型转换static_cast<int>(f)时,才会调用该函数.
总结

  1. cpp中,一个参数的构造函数(或者除了第一个参数外其余参数都有缺省值的多参构造函数),承担了两个角色。
  • 用于构建单参数的类对象
  • 隐含的类型转换操作符
  1. explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了
  2. 声明为explicit的构造函数不能在隐式转换中使用,只能显示调用,去构造一个类对象。
Base base(‘a’) //显示调用,OK
Base base = ‘a’ //隐式调用,err 调用了单参数的有参构造函数,或多参数,但后面几个参数有缺省值的
  1. 尽量避免有二义性的类型转换,如果类中包含一个或多个隐式类型转换,则必需使用explicit关键字确保在类类型和目标类型之间只存在唯一一种隐式转换方式,否则将出现二义性。
  2. 但是将拷贝构造函数声明成explicit并不是良好的设计,一般只将有单个参数的constructor声明为explicit,而copy constructor不要声明为explicit.

39)extern

1、

当出现extern “C”时,表示 extern “C”之后的代码按照C语言的规则去编译;

当extern修饰变量或函数时,表示其具有外部链接属性,即其既可以在本模块中使用也可以在其他模块中使用。

为什么会出现 extern “C" ?因为C不支持函数重载,而cpp支持函数重载,所以两类编程语言在汇编时对函数的处理方式不一样。c语言是用函数名来匹配,cpp使用函数名+形参列表来匹配。对于函数Func(int i,int j),c语言的汇编结果为Func,cpp汇编结果为_Z8Funcii,这样如果c和cpp混编的话就会出现链接时找不到函数定义的情况。
注意:除了函数重载外,extern “C”不影响cpp的其他特性

//一般情况,extern "C"是和函数声明放在一起,即位于h头文件中
//情况1:在cpp中调用c代码的函数
c文件中有一个函数: void Transfer(int a,char b);
cpp文件必须用extern "C"声明该函数如下才可以实现调用:
extern "C" void Transfer(int a,char b);

//情况2:c调用cpp代码的函数
c文件若要调用,就必须在cpp文件中用extern "C"来声明该函数,否则cpp在编译过程中就会对其进行名字改编,c文件就找不到该函数的原型
cpp文件中有一函数:
        void Transfer(int a; char b);
但必须用extern "C"来声明后,如下:
        extern “C” void Transfer(int a; char b);
c文件才可以调用void Transfer(int a; char b)函数。

//附加说明1.cpp文件中引用c的头文件:
 // cpp code
  extern “C” {
  #include “my-header1.h”
  #include “my-header2.h”
  }
//附加说明2
c的头文件如果想被cpp文件使用的话:
#ifdef __cplusplus
extern "C" {
#endif
 ...   // 按照C语言的规则去编译,C语言的头文件声明放在此处
#ifdef __cplusplus
}
#endif
使用附加说明1,cpp代码能正确使用c的头文件。使用附加说明2,该c头文件可被c和cpp正确使用。两种情况,仍选其一即可。

2、extern关键字的常规使用
extern是用来声明全局变量或者全局函数的,需要注意,是声明,不是定义
extern int i; //声明变量i,但没分配存储空间,还不能使用,还有int i; //定义了变量i,并分配了空间,可以使用。注意:在程序中一个变量可以声明多次,但只能定义一次。如果声明时有初始化式,也会被当做定义,例如:
extern int i = 5; //定义了变量5 ,说明:这样做不规范,会被报警告
后面的程序中若再出现extern int i = 5;或者int i;的语句,就会出错,因为变量只能定义一次。

关于extern的进一步说明:对于普通定义的变量,常规定义正常使用;在其他文件中要使用此变量时,需要先加extern做声明,编辑器才会在当前文件以外的文件中去寻找该变量或函数的定义,例如:

file1:
	int i=10;
file2:
	extern int i;
	i=2;

特别地:对于被const修饰的变量或函数而言,这个变量/函数默认为“局部的”。即使在其他文件中使用extern修饰了也无法使用。如果想在其他文件中使用这个变量,必须在定义该const变量时,显式地指出它将用于外部文件:

file1:
extern const int i=10;
file2:
extern const int i;

说明:extern与static的功能是相对的。extern用于在程序中使用其他文件中定义的变量或函数,1)static修饰函数,用于将函数的使用限制在当前文件内。2)static修饰全局变量也是同样的道理,限制变量的使用区域。3)static修饰局部变量,那变量的创建空间就从栈区变更到了静态数据区,可用于多次调用某个函数时,函数内计数值的递增。

40)mutable

mutable就是为了突破成员函数 const的限制,可以在const函数里面来修改被mutable修饰的成员变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值