1.引用的概念
1.定义引用必须初始化
2.定义引用时不能是空引用,没有空引用这个概念
3.没有引用的引用,即二级引用,但是有指针的指针,即二级指针
2.引用不分配空间
相当于有两个名字,一个是a另一个是b,对a操作就相当于对b操作,对b操作就相当于对a操作,可以认为a就是b,b就是a
3.不存在二级引用的概念,引用不存在二级引用,但是指针存在着二级指针,引用和指针还是有所差别的
int a = 10;
int& b = a;
int&& c = b;
4.常引用,不能通过常引用去改变变量的值
int a = 10;
int& b = a;
const int& c = a;
b += 10;
a += 10;
c += 10; //error
5.①下面代码能不能编译通过?
int main()
{
const int a = 10;
int& b = a;
}
答案:不能,因为常量a本身只可读不可写,而定义了一个引用之后,就可以通过引用可读可写了,这样的话相当于权限扩大了,这是不正确的
②下面代码能不能通过?
int main()
{
int a = 10;
const int& b = a;
}
答案:可以,变量a的权限为可读可写,而定义了一个引用b,b的权限是只可读不可写,相当于权限缩小了,这是可以的
6.判断下面Swap函数中形参列表中的引用为什么不用初始化?
void Swap(int& a,int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 10,y = 20;
Swap(x, y);
return 0;
}
答案:函数在没有调用的时候,引用并不存在,当调用Swap函数的时候,才会用实参y去初始化引用b,用实参x去初始化引用a,这种结构在Java中并不存在,只在C++中存在
7.inline函数(内联函数)
①在C++中,为了解决一些小函数频繁调用消耗大量栈空间(栈内存)的问题,引入了inline修饰符,表示为内联函数
②内联函数的处理方式是在函数的调用点直接将代码展开。在计算机系统下,假如频繁的调用就会造成较大的时间开销,内联函数的引入减少了函数调用过程中开栈和清栈的开销
③时间开销指的是:主要是现场保护和现场恢复,开栈和清栈的开销
④普通函数和内联函数调用的区别
左图普通函数调用点,函数的处理流程先进性实参压栈然后处理函数体
右图内联函数调用点,函数没有开辟和清栈,函数体直接在调用点代码展开
⑤普通函数调用和内联函数的区别
(1)内联函数在调用点代码直接展开,没有开栈和清栈的开销,普通函数有开栈和清栈的开销
(2)内联函数体要求代码简单,不能包含复杂的结构控制语句
(3)如果内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行
⑥内联函数和static修饰的函数的区别
(1)static修饰的函数处理机制只是将函数的符号变成局部符号,函数的处理机制和普通函数相同,都有函数的开栈和清栈开销,内联函数没有函数的开栈和清栈函数
(2)inline函数是因为代码在调用点直接展开导致函数本文件可见,而static修饰的函数是因为函数符号从全局符号变成局部符号导致函数本文件可见
⑦(面试常问)内联函数和宏的区别
(1)inline函数的处理时机是在编译阶段处理的,有安全检查和类型检查,而宏的处理是在预编译阶段处理的,没有任何检查机制,只是简单的文本替换
(2)inline函数是一种更安全的宏
⑧内联函数只在release版本下才会有,在debug版本下是不会有内联函数的
⑨inline函数的规则
(1)inline函数一般写在头文件中
(2)inline函数只在release版本生效,在debug版本不生效
(3)inline函数只是给编译器的一个建议,具体是否处理成inline函数是编译器决定的
(4)如果函数内部有if语句和循环语句,或者函数过大,代码超过5行,就不能处理为内联,只有完成的功能很单一的时候,只有1行或者2行的时候,才会处理为内联,如:int Max(int a,int b) {return a>b?a:b;},有的编译器当内联函数中的行数过多,就会报错,认为这样的函数不能处理为内联函数
8.函数参数默认值
①默认值必须从右向左依次赋初始值,给e赋初始值就意味着可以给d赋初始值,给d赋初始值就意味着可以给c赋初始值,依次进行,不能跳过
②当调用fun函数的时候只有3个实参,那么e的值为0,d的值为0,c的值为34,b的值为23,a的值为12,就是说5个参数,如果2个参数有默认值,调用的时候只需要传大于等于3个参数,小于等于5个参数,编译器就可以编译通过
void fun(int a,int b,int c,int d = 0,int e = 0)
{}
int main()
{
fun(12,23,34);
}
③默认值是在编译期确定的
④下面程序的输出结果为多少?
void fun(int a,int b,int c,int d = 0,int e = 0)
{
printf("%d %d %d %d %d \n",a,b,c,d,e);
}
int main()
{
fun(12,(23,34,45),56);
}
答案:输出结果为12 45 56 0 0,逗号运算符取最后一个逗号后的值
9.函数的重载
①C++区分函数是通过函数原型来区分的,函数原型和函数名是不一样的
函数原型由函数返回类型 + 函数名 + 参数列表(由参数的类型和参数的个数构成)构成,且不需要函数体
②C语言区分函数只是通过函数名来区分,C语言直接在函数名前面加一个下划线,如Max函数,就是_Max
③C语言调用约定
在函数名前返回类型中间加上__cdecl
④C++调用约定
__stdcall调用约定:
void—X
char—D
unsigned char—E
short—F
int—H
unsigned int-I
long int—J
unsigned lont int—K
float—M
double—N
bool—N
@@YA 是C的调用约定
@@YG 是C++的调用约定
@Z 代表结束,如果函数没有参数,则以Z标识结束
?+函数名+调用约定+返回值类型+第一个参数类型第二个参数类型…+结束符
char Max(int a,int b)//生成的名字是?Max@@YADHH@Z
{
return a>b?a:b;
}
int Max(int a,int b)//生成的名字是?Max@@YAHHH@Z
{
return a>b?a:b;
}
int main()
{
int a = 10;
int b = 20;
Max(a,b);
return 0;
}
⑤C++可以重载的根本原因:C++对函数进行了重命名,用到了名字粉碎技术,也叫名字重命名规则
10.外部关键字extern
意思是说,本函数在本工程的其他文件中定义了
extern int Max(int a,int b)
{
return a>b?a:b;
}
意思是说本函数按照C的方式进行编译,生成的符号就是_Max
extern"C" int Max(int a,int b)
{
return a>b?a:b;
}
11.函数名相同,参数列表相同,返回类型不同,能不能构成重载?
答案: 不能
不能进行重载的根本原因是:调用的二义性,虽然从名字的粉碎性技术上来讲,形成的名字是不一样的,但是,调用Max函数的时候,不知道调用哪个Max函数,即存在二义性
12.C++重载的根本原因
重载的根本原因是:在编译器通过重命名规则生成了不同的符号,C++符号的生成是根据函数名+参数列表(只需要类型,而不需要名称)+返回类型构成的,但是要注意,如果函数名相同,参数列表相同,返回类型不同,是不能构成重载的,原因是调用的时候存在二义性,不知道调用哪个函数
13.C语言不能重载的根本原因
C语言编译的时候生成的名字是在函数名前面加一个下划线,调用的时候就不能区分应该调用哪个函数,所以C语言函数不能重载
14.模板函数(函数模板)主要解决函数的重载问题
①template 和 template 是等价的
注意:template 是不可以的
②编译期生成代码以后,不能再被修改
③在编译期会根据实参的类型推演出类型,然后生成相应的函数,会生成一个typedef int Type
④判断a和b的类型分别是什么?
template<class Type>
void fun(Type p)
{
Type a,b;
}
int main()
{
int x = 10;
int* p = &x;
fun(p);
return 0;
}
答案:a是int *类型 ,b是int类型,不能理解为都是int *类型,这种理解是错误的,不是宏的替换过程,是类型重命名过程
生成的是
typedef int * Type
void fun<int *>(Type p)
{
Type a,b;
}
生成的不是,这种理解是错误的,不是宏的替换原则
void fun(int *p)
{
int * a,b;
}
15.free,delete是指把指针指向的堆空间还给了系统,所以要记住把指针置为空,否则为野指针,容易出现问题
*16.int p = new int [n];new申请失败之后,会抛出一个异常,所以不能用 if(p == NULL) 来判断是否申请成功
*但是如果写成int p = new (nothrow) int [n];是可以用 if(p == NULL) 来判断的
用malloc申请失败以后,会返回NULL,是可以用 if(p == NULL) 来判断的
int *p = new (nothrow) int [n];//表示不需要抛出异常,我不关心
17.命名空间:namespace
命名空间是为了解决全局变量名污染问题,就是名字重复问题
:: 是作用域解析符
18.面向对象
在C++中,类是一种数据类型,在现实生活中好比图纸
我们是在设计类型,不是在定义类型
访问限定符有三种:public(公共的)、public(私有的)、protected(保护的)
第一种说明的成员能从外部进行访问,另外两种说明的成员不能从外部进行访问,每种说明符可在类体种使用多次,他们的作用域是从该说明符出现开始到下一个说明符之前或类体结束之前结束
访问说明符体现了类的封装性
定义的类好比当作一个图纸,如果要访问图纸里面的内容,即要访问类中的成员,就必须先实例化,才能访问类中的成员
类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化,类中的任何数据成员也布恩那个使用关键字extern、auto或register限定其存储类型
函数定义通常在类的说明之后进行
判断g_sum的值为多少?
int g_sum = 0;
int main()
{
int a = 10;
int g_sum = 10;
printf("g_sum = %d\n",g_sum);
return 0;
}
答案:10,编译的时候会用局部变量的值替换掉全局变量的值
判断最终a的值为多少?
int g_sum = 0;
int main()
{
int a = 10;
int g_sum = 10;
a = ::g_sum;
printf("g_sum = %d\n",g_sum);
return 0;
}
答案:a = ::g_sum;会把全局变量g_sum的值赋给a,而不是局部变量g_sum的值赋给a
空类的大小为什么是1而不是0?
解析:标记类存在