欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【C++研发面试笔记】1. C++常见关键字含义

程序员文章站 2022-05-23 07:54:16
【C++研发面试笔记】1. C++常见关键字含义 1.1 inline 定义内联函数,该关键字是基于定义的。如果只在函数声明时给出inline,则函数不会被认为是内联函数,所以...

【C++研发面试笔记】1. C++常见关键字含义

1.1 inline

定义内联函数,该关键字是基于定义的。如果只在函数声明时给出inline,则函数不会被认为是内联函数,所以必须在函数定义的地方也加上inline。其主要特性:

一个函数若声明inline,则每处声明都必须保证是inline,类成员函数若在类定义内给出定义,则隐含inline。 声明定义内联函数时,编译器将所调用的代码嵌入到主调函数中。编译器在必要或更符合预期的目标代码质量,其也有权不实际内联。

1.2 const

定义常量成员,包括const数据成员和const成员函数,const数据成员必须也只能通过构造函数的初始化列表进行初始化。
const成员函数只能访问类的成员,不能进行修改,如果需要修改,则引入下面的mutable关键字。

1、const修饰普通变量(两种写法都类似)
const TYPE value;
TYPE const value;

2、const修饰指针
指针本身是常量不可变:(char*) const pContent;
指针所指向的内容是常量不可变:const (char) *pContent;或(char) const *pContent;
两者都不可变:const char* const pContent;

3、const修饰函数
const修饰函数参数是它最广泛的一种用途,它表示在函数体中不能修改参数的值(包括参数本身的值或者参数其中包含的值)

4、const修饰类对象/对象指针/对象引用
const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。

5、const修饰数据成员
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。

6、const修饰成员函数
const修饰类的成员函数,用const修饰的成员函数不能改变对象的成员变量。一般把const写在成员函数的最后。

7、const常量与define宏定义的区别

A. 编译器处理方式不同:define宏是在预处理阶段展开;const常量是编译运行阶段使用。 B. 类型和安全检查不同:define宏没有类型,不做任何类型检查,仅仅是展开;const常量有具体的类型,在编译阶段会执行类型检查。 C. 存储方式不同:define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存;const常量会在内存中分配(可以是堆中也可以是栈中)。

8、使用单独的变量初始化const引用
使用单独的变量初始化const引用的值不会产生额外的存储空间,通过修改原先的变量是可以修改常量引用的值的。


1.3 mutable

mutable也是为了突破const的限制而设置的。
被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。
这个关键字的引入是解决const成员函数要修改成员变量,通常而言,const成员函数只能访问成员变量,不能修改成员,但是如果成员变量被mutable修饰了,则在const成员函数中可以修改该变量。
mutable和const不能同时用于修饰成员变量。


1.4 static

声明静态成员,包括静态数据成员和静态成员函数,它们被类的所有对象共享,静态数据成员在使用前必须初始化,而静态成员函数只能访问静态数据成员,不能访问非静态数据成员,因为该函数不含有this指针。
1. 面向过程的static关键字
(1)静态全局变量:
全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。

1、该变量在全局数据区分配内存; 2、未经初始化的静态全局变量会被程序自动初始化为0; 3、静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的。其它文件中可以定义相同名字的变量,不会发生冲突。定义全局变量就可以实现变量在文件中的共享。

TIPS:一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。

(2)静态局部变量:
在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次函数调用,直到下次赋新值。
静态局部变量有以下特点:

1、该变量在全局数据区分配内存; 2、静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化; 3、静态局部变量一般在声明处初始化,如果没有显式初始化,程序自动初始化为0; 4、它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。

(3)静态函数:
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。其它文件中可以定义相同名字的函数,不会发生冲突。

2. 面向对象的static关键字
(1)静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。
对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。

1、对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。 2、静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。 3、静态数据成员和普通数据成员一样遵从public,protected,private访问规则。 4、静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象。 5、在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它; 6、静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为: <数据类型><类名>::<静态数据成员名>=<值> 7、类的静态数据成员有两种访问形式: <类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名> 8、静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性。

(2)静态成员函数
与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。
普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。
关于静态成员函数,可以总结为以下几点:

1、出现在类体外的函数定义不能指定关键字static; 2、静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数; 3、非静态成员函数可以任意地访问静态成员函数和静态数据成员; 4、静态成员函数不能访问非静态成员函数和非静态数据成员; 5、由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长; 6、调用静态成员函数,可以用成员访问操作符来调用静态成员函数,也可以直接使用如下格式: <类名>::<静态成员函数名>(<参数表>)

1.5 virtual

声明虚函数,用于实现多态,该关键字是基于声明的。
所谓虚函数,虚就虚在“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。
由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被称为“虚”函数。只要在基类中已声明为virtual,这里即使不使用virtual关键字,默认也是虚函数。同样,如果还有从子类派生的子类,对应的成员函数也是虚函数。

1. 虚函数
虚函数只能借助于指针或者引用来达到多态的效果
通过这些手段,编译器在看到一个虚函数调用的时候,就将会在运行时刻决定调用哪个函数。虚函数必须要被定义。

2. 虚析构函数
析构函数也可以是虚的,甚至是纯虚的。当一个类打算被用作其它类的基类时,它的析构函数必须是虚的。在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。而直接用继承类的指针去操作继承类的成员,却不会出现这种情况。

3. 纯虚函数
如下声明表示一个函数为纯虚函数(纯虚函数也可以有定义)(如果一个类里面有一个或多个纯虚函数,这个类就是抽象类)。纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。而试图创建一个抽象基类的独立类对象会导致编译时刻错误。
virtual void foo()=0; // =0标志一个虚函数为纯虚函数

4. 构造函数和析构函数中的虚函数调用
一个类的虚函数在它自己的构造函数和析构函数中被调用的时候,它们就变成普通函数了。也就是说不能在构造函数和析构函数中让自己“多态”。这是因为派生类对象中构造函数的调用顺序是,先调用基类的构造函数,然后是派生类的构造函数。在基类析构函数中也是如此。

5. 虚函数与纯虚函数的区别

虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。 虚函数可以被直接使用(必须被定义),也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义。 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。 虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。 定义了纯虚函数的类称为抽象类,抽象类不能被实例化。

1.6 friend

声明友元函数和友元类,该关键字也是基于声明的。
采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。

1. 友元函数
有时需要定义一些函数,这些函数不是类的一部分(注意友元函数不是类的一部分),但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该类的友元函数。
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
friend 类型 函数名(形式参数);

友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。 一个函数可以是多个类的友元函数,只需要在各个类中分别声明。友元函数的调用与一般函数的调用方式和原理一致。 友元函数并不是类的成员函数,因此在类外定义的时候不能加上class::function name

2.友元类
除了友元函数外,还有友元类,两者统称为友元。友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。同友元函数一样,其也需要在类中加上关键字friend声明。
友元类的所有成员函数都是类的友元函数,能存取类的私有成员和保护成员。

友元关系不能被继承。 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。

1.7 volatile

volatile的本意是“易变的”,volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。可以与const同时修饰一个变量。

当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被寄存。volatile可以保证对特殊地址的稳定访问。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

一个参数既可以是const还可以是volatile,一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。


1.8 struct, class, union

class是一般的类类型。struct在C++中是特殊的类类型,声明中仅默认隐式的成员和基类访问限定与class不同(struct是public,class是private)。union是联合体类型。class还有个用途是在模版类型声明中作为表示模版类型参数或模版模版参数的语法的必要组成部分,等同于typename。

至于union在很多地方跟struct类似,但有一些特别的地方。

一个时点上只有一个成员可用,(换句话说,所有成员使用的是同一块内存,即所有成员公用的一个地址。)。 不能做基类,也就没有虚函数。 不能有静态和引用成员。 不能嵌套类(实际上是不能拥有具有构造函数、析构函数、赋值操作的成员,如果仅仅是数据封装还是可以的)。

1.9 extern

extern意为“外来的”,是存储类声明修饰符。其有两个作用:

当它与”C”一起连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的”脾气”了(不同的编译器采用的方法不一样),主要是因为C++支持函数的重载。 当extern不与”C”在一起修饰变量或函数时,如在头文件中: extern int g_Int;它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块引用其他模块中的定义,记住它是一个声明不是定义,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。这样的用处还是有的,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,我比较习惯在所有的函数声明前添加extern修饰。

1、extern 和 static
(1)extern 表明该变量在别的地方已经定义过了,在这里要使用那个变量。
(2)static 表示静态的变量,分配内存的时候,存储在静态区,不存储在栈上面。
static 作用范围是内部连接的关系, 和extern有点相反。static与extern是一对“水火不容”的家伙,也就是说extern和static不能同时修饰一个变量。

2、extern 和const
C++中const修饰的全局常量据有跟static相同的特性,即它们只能作用于本编译模块中,但是const可以与extern连用来声明该常量可以作用于其他编译模块中, 如extern const char g_str[];


1.10 template

声明一个模板、模版的特化或显式实例化。模版用于打破类型系统的某些限制,推迟类型检查到实例化得到具体的模版实例进行以复用代码,实现泛型和参数化编程


1.11 this

this是一种实体,仅在类的非静态成员中使用,是指向类的对象的指针右值。


1.12 typedef

用以给数据类型取别名。字面名义上是定义,实际是声明,这点和C语言的说法不同。


1.13 typeid

获取表达式的类型,以std::type_info表示结果,可能抛出std::bad_typeid。当操作数非多态类(引用)类型在编译时即可确定结果,否则需要在运行时取得结果,即RTTI。

从名字直观看来,该关键字应该是获取语言元素的类型ID。其实它和sizeof类似,是一个类型运算符。有时候代码可能需要获取某个变量或者类型的名字,这时候使用typeid就比较合适。

使用格式:typeid(int)或typeid(i+1)
这样操作后返回有个type_info类型的对象,比较常用的对象成员函数一般有比较是否相等和获取类型名。


1.14 typename

告诉编译器一个嵌套的限定名(包含::)中的未知的标识符是一个类型。这只在模板中需要区分依赖名称时使用。另一种用法是在模版声明参数列表中表示模版类型参数,可被class代替。

1、第一种情况是在函数模板和类模板声明中
一般模板声明中,使用class关键字指定类型参数,后来C++支持使用typename代替class关键字。这里仅仅是在语义上强调模板使用的类型参数不一定是类类型,可以是所有类型。这里typename和class没有任何区别。使用格式:
templatetemplate完全等价!

2、第二种情况使用情况比较特殊
简单说起来就是在使用类内成员类型的时候。类内成员类型就是在类定义内声明了一个类型,该类型属于类型内部,可见性由权限访问符限定。使用格式:
typename T::MyType * pvar;和typedef typename T:: MyType MyType;


1.15 asm

用于语法:
asm-definition: asm ( string-literal );
意义由实现定义,典型实现中传输其中的字符串给汇编器。


1.16 auto

在C++98/03中这个这个关键字用于声明块中的变量的生存期为自动生存期,若是对象同时具有自动存储类,即生存期在块结束时结束。这样的变量被称为局部变量。这个关键字不常用,因为即便省略,声明的默认就是auto的。

在C++11中,auto的含义改变为自动通过初值符推断声明的类型占位符。如声明auto i = 1;,auto就相当于int,因为1是int类型,可以推断出i的类型。也可以使用auto& i等声明,具体推导规则同模版参数类型推导。


1.17 *_cast

显式类型转换,C++延续了C风格的强制类型转换的语法。dynamic_cast是动态的,需要运行时支持;其它都是静态检查,相比C风格的类型转换更加细化,增强了类型安全性。C++支持四种关键字对不同形式的类型转换进行分别处理。使用格式:
转换关键字<类型>(表达式)

1、static_cast和C风格类型转换功能完全相同,它属于在编译时期静态的类型转换。如果把一个double类型转换为整形,形式如下:
static_cast(0.1);
static_cast功能有所限制,比如不能转化struct类型到int,不能转化指针到double等。另外,它不能在转换中消除const和volatile属性。

2、const_cast用于消除引用或者指针的const或者volatile属性。
const int &ci=100;
int &i=const_cast(ci);
通过这种方式,ci引用的内存单元虽然无法通过修改ci改变,但是可以修改i改变内存的值。这里是把const属性消除,这里想多说一点的是把const加上的问题。
&>

3、dynamic_cast一般出现在类到子类或兄弟类的转换,并要求基类有虚函数。而且它能提供转换后的结果和状态,一旦转换失败则返回空指针。如果没有继承关系的转换一般使用static_cast。

4、reinterpret_casts一般用作函数指针的转换,而且使用它的代码可移植性很差,因为无法确定编译器的函数调用方式等。有可能会导致函数调用出错,一般不常用。


1.18 explicit

explicit关键字只能用于修饰只有一个参数的类构造函数,它的作用是表明该构造函数是显示的,而非隐式的,跟它相对应的另一个关键字是implicit,意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。

explicit关键字只对有一个参数的类构造函数有效,如果类构造函数参数大于或等于两个时,是不会产生隐式转换的,所以explicit关键字也就无效了,而除了第一个参数以外的其他参数都有默认值的时候,explicit关键字依然有效。

除非有心利用,隐式转换常常带来程序逻辑的错误,而且这种错误一旦发生是很难察觉的。原则上应该在所有的构造函数前加explicit关键字,当你有心利用隐式转换的时候再去解除explicit,这样可以大大减少错误的发生。

隐式转换的含义:在C++中,如果的构造函数只有一个参数时,那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象。也就是说CxString string2 = 10;这段代码,编译器自动将整型转换为CxString类对象,实际上等同于CxString string2(10);


1.19 export

导出模版,用于分离编译。C++11废除了这个export关键字的含义,但保留这个关键字。


1.20 register

提示声明的对象被放入寄存器中以便得到更好的性能。同inline类似,并非强制;不同的是这个提示经常被现代的编译器无视,因此C++11中被标记为过时的。