c++基础
1.命名空间域(namespace)
命名空间是为了避免命名冲突,命名污染,可以在不同的空间域定义相同的函数名。
命名空间域存在访问限制,如使用c++库 std 库时可以通过using namespace std; 进行访问,但是这种直接将标准库暴露的方式访问并不安全,因此可以采用 using std::cout 这种方式进行引用。
2.缺省参数
在c++中函数传参可以给形参一个缺省参数,这样方便我们在函数传参的时候万一不知道传什么就可以直接将该缺省参数自动 传入函数。
1.缺省参数分为半缺省和全缺省.
2.缺省参数只能写在函数声名中不能写在函数定义中。
3.缺省参数必须是常量或者全局变量
//全缺省
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
//半缺省
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
缺省参数在缺省时只能依次从右向左缺省,而传参的时候只能省略传入缺省参数的。
3.函数重载
c++支持函数重载而c语言不支持。
底层原因是因为,c++有一套新的函数命名规则,可以根据函数传入参数的不同在编译链接阶段的时候形成不同的地址,函数声明通过链接该地址的方式完成链接.形成.o文件。
函数重载分为3种:
1.参数个数不同
2.类型不同
3.类型顺序不同
//1 参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
c++和c可以相互调用,但是由于c,c++编译器对函数名修饰规则不同,所以相互调用的时候需要进行特殊处理。
c++调用c的时候需要加上"entern c"
c调用c++库时同样需要此操作,但是需要加一些条件编译指令对"extren c"进行隔离,因为c不认识c++中的"extren c".
4 引用(&)及引用和指针的区别
c++的引用相当于取别名。引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
c语言的指针和c++引用十分类似,函数中传指针和传引用的目的是为了减少直接传值时拷贝产生的临时变量出现的空间消耗。
引用的特性
1.引用在定义时必须初始化。
2.一个变量可以有多个引用。
3.引用一旦引用了实体,就不能引用其他实体。
常引用--(临时变量具有常性)
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
引用时需主要不要对权限进行放大,可以对权限进行缩小,可以经常用const引用避免对权限放大,例如,将一个dobule类型的值使用int &类型的时候此时会进行隐式类型转换,中间会生成一个临时变量 而临时变量具有常性,所以需要const int &才可以
void TestRef()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
引用的2种使用场景
1.做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
2.做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
函数直接返回时,进入函数时会在栈帧上创建一个临时变量,当函数调用结束时,此创建的临时变量就会销毁。当函数需要返回该临时变量时,该临时变量会形成一个临时拷贝传给接收值,此时形成临时拷贝会产生消耗,并且当对接收值进行操作时,可能会触发越界访问,因为临时变量已经销毁,接收值依然指向销毁前临时变量的地址。如何形参传值,形参改变并不影响实参。
函数如果传引用返回需要对函数内部的临时变量用static修饰。因为static修饰的变量会存放在静态区,传该变量的别名返回。
引用和指针的区别
1.在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
2.概念上引用定义一个变量别名,指针存储一个变量地址。
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
4.没有空引用,但有空指针。
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7.有多级指针,但没有多级引用。
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
9.引用比指针使用起来相对更安全。
5.内联函数(inline)
概念:如果一个函数定义成内联函数,那么该函数会在调用的地方展开,(但是内联函数只是给编译器一个建议,具体函数实现中能否成为内联函数主要依据是编译器在编译阶段展开后形成的汇编指令大小,太大则编译器不会采用内联)。
优点:其实是一种以空间换时间的方式,内联函数会减少函数频繁调用时产生的栈帧消耗,会直接在调用内联函数的地方展开
缺点:会增加函数的冗余性。
注意:内联函数一般不支持声明和定义分离,不然会出现链接错误。内联函数会在调用时展开,就没有函数地址,就会链接不到。
宏(#define)
c++中内联函数是为了填宏的坑。(常用congst ,enum替换宏)
宏的缺点:不方便调试,代码可读性差,容易误用,没有类型安全检查。
宏的优点:增强代码的复用性,可维护性强。
6.auto
c++关键字auto:可以自动推导类型,常用于范围for,迭代器等。
auto使用规则:
1.使用auto变量时必须对其进行初始化!!
2.auto可以和指针引用类型结合起来使用,auto 和auto*没有区别,但是在用auto声明引用类型时必须加&。
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
3.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
4.auto不能作为函数参数。
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
5.auto不能直接用来声明数组
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
7.范围for
常用于遍历数组或其他容器,底层实现为迭代器。
常用用法
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
范围for使用条件:
1.范围for 的迭代范围是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
//因为数组传参传数组名其实就是首元素地址,因此for的范围不确定
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
3.空指针(nullptr)
因为NULL其实是宏定义,NULL在某些环境中是0的意思。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。