面试题:C++ 对C的扩展最全总结

 

c++语言在c语言的基础上添加了面向对象编程泛型编程的支持。c++继承了c语言高效,简洁,快速和可移植的传统。

c++融合了3种不同的编程方式:

  • c语言代表的过程性语言.
  • c++在c语言基础上添加的类代表的面向对象语言.
  • c++模板支持的泛型编程。

目录

一、 ::作用域运算符

二、 名字控制

2.1 C++命名空间(namespace)

2.2 using声明

三、全局变量检测增强

四、 C++中所有的变量和函数都必须有类型

五、更严格的类型转换

六、struct类型加强

七、“新增”bool类型关键字

八、三目运算符功能增强

九、 C/C++中的const

 9.1 C中的const

9.2 C++中的const

9.3 C/C++中const异同总结

9.4 尽量以const替换#define

十、引用

10.1 引用基本用法

10.2 函数中的引用

10.3 引用的本质

10.4 指针引用

10.5 常量引用

十一、 内联函数(inline function)

11.1 类内部的内联函数

11.2 内联函数和编译器

十二、函数的默认参数

十三、函数的占位参数

十四、函数重载(overload)


 

 

 

一、 ::作用域运算符

通常情况下,如果有两个同名变量,一个是全局变量,另一个是局部变量,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。

//全局变量
int a = 10;
//1. 局部变量和全局变量同名
void test(){
	int a = 20;
	//打印局部变量a
	cout << "局部变量a:" << a << endl;
	//打印全局变量a
	cout << "全局变量a:" << ::a << endl;
}

 

二、 名字控制

创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它会不可避免地包含大量名字。c++允许我们对名字的产生和名字的可见性进行控制。

 

2.1 C++命名空间(namespace)

  • 创建一个命名空间:
namespace A{
	int a = 10;
}
namespace B{
	int a = 20;
}
void test(){
	cout << "A::a : " << A::a << endl;
	cout << "B::a : " << B::a << endl;
}

注意:

  • 命名空间只能全局范围内定义
  • 命名空间可嵌套命名空间
  • 命名空间是开放的,即可以随时把新的成员加入已有的命名空间中
  • 声明和实现可分离
  • 无名命名空间,意味着命名空间中的标识符只能在本文件内访问,相当于给这个标识符加上了static,使得其可以作为内部连接

 

2.2 using声明

如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合。

namespace A{
	int paramA = 20;
	int paramB = 30;
	void funcA(){ cout << "hello funcA" << endl; }
	void funcB(){ cout << "hello funcA" << endl; }
}

void test(){
	//1. 通过命名空间域运算符
	cout << A::paramA << endl;
	A::funcA();
	//2. using声明
	using A::paramA;
	using A::funcA;
	cout << paramA << endl;
	//cout << paramB << endl; //不可直接访问
	funcA();
}

 

三、全局变量检测增强

c语言代码:

int a = 10; //赋值,当做定义
int a; //没有赋值,当做声明

int main(){
	printf("a:%d\n",a);
	return EXIT_SUCCESS;
}

此代码在c++下编译失败,在c下编译通过.

 

四、 C++中所有的变量和函数都必须有类型

  1. C语言中,int fun() 表示返回值为int,接受任意参数的函数,int fun(void) 表示返回值为int的无参函数。
  2. 在C++ 中,int fun() 和int fun(void) 具有相同的意义,都表示返回值为int的无参函数。

 

五、更严格的类型转换

在C++,不同类型的变量一般是不能直接赋值的,需要相应的强转。

 

六、struct类型加强

  1. c中定义结构体变量需要加上struct关键字,c++不需要。
  2. c中的结构体只能定义成员变量,不能定义成员函数。c++即可以定义成员变量,也可以定义成员函数。
//1. 结构体中即可以定义成员变量,也可以定义成员函数
struct Student{
	string mName;
	int mAge;
	void setName(string name){ mName = name; }
	void setAge(int age){ mAge = age; }
	void showStudent(){
		cout << "Name:" << mName << " Age:" << mAge << endl;
	}
};

//2. c++中定义结构体变量不需要加struct关键字
void test01(){
	Student student;
	student.setName("John");
	student.setAge(20);
	student.showStudent();
}

 

七、“新增”bool类型关键字

标准c++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0)表示状态。这三个名字都是关键字。

  1. bool类型只有两个值,true(1值),false(0值)
  2. bool类型占1个字节大小
  3. 给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)

 

八、三目运算符功能增强

  1. c语言三目运算表达式返回值为数据值,为右值,不能赋值。
  2. c++语言三目运算表达式返回值为变量本身(引用),为左值,可以赋值。
        int a = 10;
	int b = 20;
	printf("ret:%d\n", a > b ? a : b);


	cout << "b:" << b << endl;
	//返回的是左值,变量的引用
	(a > b ? a : b) = 100;//返回的是左值,变量的引用
	cout << "b:" << b << endl;

 

九、 C/C++中的const

 

 9.1 C中的const

C中的const 是伪常量,

const int arrSize = 10;
int arr[arrSize];

看似是一件合理的编码,但是这将得出一个错误。 因为arrSize占用某块内存,所以C编译器不知道它在编译时的值是多少?

9.2 C++中的const

在c++中,一个const不必创建内存空间,而在c中,一个const总是需要一块内存空间。

一般说来,如果一个const仅仅用来把一个名字用一个值代替(就像使用#define一样),那么该存储局空间就不必创建。

不过,取一个const地址, 或者把它定义为extern,则会为该const创建内存空间。

 在c++中,出现在所有函数之外的const作用于整个文件(也就是说它在该文件外不可见),默认为内部连接,c++中其他的标识符一般默认为外部连接。

 

9.3 C/C++中const异同总结

  • c语言全局const会被存储到只读数据段。c++中全局const当声明extern或者对变量取地址时,编译器会分配存储地址,变量存储在只读数据段。两个都受到了只读数据段的保护,不可修改。
  • c语言中局部const存储在堆栈区,只是不能通过变量直接修改const只读变量的值,但是可以跳过编译器的检查,通过指针间接修改const值。
  • c++中对于局部的const变量要区别对待:
  1. 对于基础数据类型,也就是const int a = 10这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存。
  2. 对于基础数据类型,如果用一个变量初始化const变量,如果const int a = b,那么也是会给a分配内存。
  3. 对于自定数据类型,比如类对象,那么也会分配内存。
  • c中const默认为外部连接,c++中const默认为内部连接

 

9.4 尽量以const替换#define

  1. const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查.
  2. const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。

 

十、引用

引用是c++对c的重要扩充。在c/c++中指针的作用基本都是一样的,但是c++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference),它也存在于其他一些编程语言中,并不是c++的发明。

c++中新增了引用的概念,引用可以作为一个已定义变量的别名。

面试题:C++ 对C的扩展最全总结_第1张图片

注意事项:

  • &在此不是求地址运算,而是起标识作用。
  • 类型标识符是指目标变量的类型
  • 必须在声明引用变量时进行初始化。
  • 引用初始化之后不能改变。
  • 不能有NULL引用。必须确保引用是和一块合法的存储单元关联。
  • 可以建立对数组的引用。

 

10.1 引用基本用法

1. 认识引用

void test01(){

	int a = 10;
	//给变量a取一个别名b
	int& b = a;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "------------" << endl;
	//操作b就相当于操作a本身
	b = 100;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "------------" << endl;
	//一个变量可以有n个别名
	int& c = a;
	c = 200;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "c:" << c << endl;
	cout << "------------" << endl;
	//a,b,c的地址都是相同的
	cout << "a:" << &a << endl;
	cout << "b:" << &b << endl;
	cout << "c:" << &c << endl;
}

2. 使用引用注意事项

void test02(){
	//1) 引用必须初始化
	//int& ref; //报错:必须初始化引用
	//2) 引用一旦初始化,不能改变引用
	int a = 10;
	int b = 20;
	int& ref = a;
	ref = b; //不能改变引用
	//3) 不能对数组建立引用,格式错误
	int arr[10];
	//int& ref3[10] = arr;
}

3.建立数组引用

//1. 建立数组引用方法一
typedef int ArrRef[10];
int arr[10];
ArrRef& aRef = arr;
for (int i = 0; i < 10;i ++)
{
	aRef[i] = i+1;
}
for (int i = 0; i < 10;i++)
{
	cout << arr[i] << " ";
}
cout << endl;

//2. 建立数组引用方法二
int(&f)[10] = arr;
for (int i = 0; i < 10; i++)
{
	f[i] = i+10;
}
for (int i = 0; i < 10; i++)
{
	cout << arr[i] << " ";
}
cout << endl;

 

10.2 函数中的引用

最常见看见引用的地方是在函数参数和返回值中。我们知道在函数中修改外部的值需要传入函数高一级的指针,才可以修改。但是这样会增加指针的难度,如果使用引用,只需要传入同级指针。

如果从函数中返回一个引用,必须像从函数中返回一个指针一样对待。当函数返回值时,引用关联的内存一定要存在。

例子:用三种方法交换2个的值

//值传递(无法交换)
void ValueSwap(int m,int n){
	int temp = m;
	m = n;
	n = temp;
}
//地址传递
void PointerSwap(int* m,int* n){
	int temp = *m;
	*m = *n;
	*n = temp;
}
//引用传递
void ReferenceSwap(int& m,int& n){
	int temp = m;
        m = n;
	n = temp;
}


void test(){
	int a = 10;
	int b = 20;
	//值传递
	ValueSwap(a, b);
	cout << "a:" << a << " b:" << b << endl;
	//地址传递
	PointerSwap(&a, &b);
	cout << "a:" << a << " b:" << b << endl;
	//引用传递
	ReferenceSwap(a, b);
	cout << "a:" << a << " b:" << b << endl;
}

通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单:   

  1. 函数调用时传递的实参不必加“&”符
  2. 在被调函数中不必在参数前加“*”符

引用作为其它变量的别名而存在,因此在一些场合可以代替指针。C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。

注意的点:

  1. 不能返回局部变量的引用。
  2. 函数当左值,必须返回引用。
//返回局部变量引用
int& TestFun01(){
	int a = 10; //局部变量
	return a;
}
//返回静态变量引用
int& TestFunc02(){	
	static int a = 20;
	cout << "static int a : " << a << endl;
	return a;
}
int main(){
	//不能返回局部变量的引用
	int& ret01 = TestFun01();
	//如果函数做左值,那么必须返回引用
	TestFunc02();
	TestFunc02() = 100;
	TestFunc02();

	return EXIT_SUCCESS;
}

 

10.3 引用的本质

引用的本质在c++内部实现是一个指针常量.

Type& ref = val;           // Type* const ref = &val;

c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。

//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
	ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
	int a = 10;
	int& aRef = a; //自动转换为 int* const aRef = &a;这也能说明引用为什么必须初始化
	aRef = 20; //内部发现aRef是引用,自动帮我们转换为: *aRef = 20;
	cout << "a:" << a << endl;
	cout << "aRef:" << aRef << endl;
	testFunc(a);
	return EXIT_SUCCESS;
}

 

10.4 指针引用

在c语言中如果想改变一个指针的指向而不是它所指向的内容,函数声明可能这样:

给指针变量取一个别名。

Type* pointer = NULL;  
Type*& = pointer;

例子:修改teacher的年龄

struct Teacher{
	int mAge;
};
//指针间接修改teacher的年龄
void AllocateAndInitByPointer(Teacher** teacher){
	*teacher = (Teacher*)malloc(sizeof(Teacher));
	(*teacher)->mAge = 200;  
}
//引用修改teacher年龄
void AllocateAndInitByReference(Teacher*& teacher){
	teacher->mAge = 300;
}
void test(){
	//创建Teacher
	Teacher* teacher = NULL;
	//指针间接赋值
	AllocateAndInitByPointer(&teacher);
	cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;       //200
	//引用赋值,将teacher本身传到ChangeAgeByReference函数中
	AllocateAndInitByReference(teacher);
	cout << "AllocateAndInitByReference:" << teacher->mAge << endl;     //300
	free(teacher);
}

对于c++中的定义那个,语法清晰多了。函数参数变成指针的引用,用不着取得指针的地址。

 

10.5 常量引用

常量引用的定义格式:

const Type  ref   val;

常量引用注意:

  1. 字面量不能赋给引用,但是可以赋给const引用
  2. const修饰的引用,不能修改。
void test01(){
	int a = 100;
	const int& aRef = a; //此时aRef就是a
	//aRef = 200; 不能通过aRef的值
	a = 100; //OK
	cout << "a:" << a << endl;
	cout << "aRef:" << aRef << endl;
}

不能把一个字面量赋给引用,但是可以把一个字面量赋给常引用

void test02(){
	//不能把一个字面量赋给引用
	//int& ref = 100;
	//但是可以把一个字面量赋给常引用
	const int& ref = 100;         //解释器翻译过来 :int temp = 100; const int& ret = temp;
}

 

    [const引用使用场景]

    常量引用主要用在函数的形参,尤其是类的拷贝/复制构造函数。

将函数的形参定义为常量引用的好处:

  • 引用不产生新的变量,减少形参与实参传递时的开销。
  • 由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。

    如果希望实参随着形参的改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常量引用。

 

十一、 内联函数(inline function)

 

内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。

在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数

内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。

在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。

inline  void  func(int a);

以上写法没有任何效果,仅仅是声明函数,应该如下方式来做:

inline  int  func(int a){return ++;}

内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压栈,跳转,返回的开销。我们可以理解为内联函数是以空间换时间

 

11.1 类内部的内联函数

为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但是在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数。

class Person{

public:

    Person(){ cout << "构造函数!" << endl; }

    void PrintPerson(){ cout << "输出Person!" << endl; }

}

构造函数Person,成员函数PrintPerson在类的内部定义,自动成为内联函数。

 

11.2 内联函数和编译器

但是c++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:

  • 不能存在任何形式的循环语句
  • 不能存在过多的条件判断语句
  • 函数体不能过于庞大
  • 不能对函数进行取址操作

内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。

 

十二、函数的默认参数

c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。

void TestFunc01(int a = 10, int b = 20){
	cout << "a + b  = " << a + b << endl;
}
//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}

int main(){
	//1.如果没有传参数,那么使用默认参数
	TestFunc01();
	//2. 如果传一个参数,那么第二个参数使用默认参数
	TestFunc01(100);
	//3. 如果传入两个参数,那么两个参数都使用我们传入的参数
	TestFunc01(100, 200);

	return EXIT_SUCCESS;
}

 

  1. 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
  1. 如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。

 

十三、函数的占位参数

c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。

void TestFunc01(int a,int b,int){
	//函数内部无法使用占位参数
	cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc02(int a, int b, int = 20){
	//函数内部依旧无法使用占位参数
	cout << "a + b = " << a + b << endl;
}
int main(){

	//错误调用,占位参数也是参数,必须传参数
	//TestFunc01(10,20); 
	//正确调用
	TestFunc01(10,20,30);
	//正确调用
	TestFunc02(10,20);
	//正确调用
	TestFunc02(10, 20, 30);

	return EXIT_SUCCESS;
}

 

十四、函数重载(overload)

实现函数重载的条件:

  1. 同一个作用域
  2. 参数个数不同
  3. 参数类型不同
  4. 参数顺序不同
//1. 函数重载条件
namespace A{
	void MyFunc(){ cout << "无参数!" << endl; }
	void MyFunc(int a){ cout << "a: " << a << endl; }
	void MyFunc(string b){ cout << "b: " << b << endl; }
	void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;}
    void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;}
}

//2.返回值不作为函数重载依据
namespace B{
	void MyFunc(string b, int a){}
	//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数
}

 

 

 

你可能感兴趣的:(c/c++)