C++学习第一课 发明背景、引用、Const、重载、命名空间
从 C 到 C++
发明背景
编程历史上有两次软件危机,实质是代码量过大,难以管理。
第一次危机是汇编时代,由于代码行数过多,工程师难以管理了,最终的解决办法是面向过程编程(函数模块化)。
第二次危机是由于面向过程、模块化手段、抽象编程都对程序员有了较高的要求。这次危机的解决是引入了面型对象编程思想。
面向对象是一种编程思想,并不特指哪一种语言,属于所有语言,只是有的语言更适合来表达该思想。如:C++(它是第一款较为流行的面向对象的语言,之后主打面向对象的语言;Java、Go、Python多少都受到了C++的影响)
C 和 C++关系
1.C++和C是两门语言。
2.C 语言是C++的子集,也就是适用于C 的语法,在 C++上都能编译通过,反之就不一定了。(最初的C++编译器,其实是将所谓的“C++”代码先编译成C代码,再继续使用C编译器,得到可执行文件内)
3.在C 语言结构体的基础上,引入了C++中的结构体、类等(C++中的经典内容)。
4.C++中有标准模版,类似C 中的标准库。
5.C++中有异常机制。
6.C11 中引入了新语法:auto、lambda
面向对象编程思想三大特征;
封装、继承、多态
引用
在C语言中,我们认识到了使用指针的方便,
比如:
使用指针,我们可以更方便的与函数交流信息。
如:使用指针调用函数,交换两个数字的值:
void SwapValue(int* p1, int* p2) { int iTemp = *p1; *p1 = *p2; *p2 = iTemp; }
但指针虽然方便,但也有不便之处
1.指针相关的运算符复杂(解引用、取地址)
2.指针指向的地址可能在运行中发生变化,从而产生bug
在C语言中,如下,是交换不了值的:
void SwapValueLiteral(int a, int b) { int iTemp = a; a = b; b = iTemp; }
但是以上代码更容易理解(直接就是操作形参名字)。所以期待发明一种语法:
1.直接操作有缘变量的名字,而不像指针那么复杂
2.可以像指针那样,将操作的效果,带出函数。
所以,就发明了引用
引用的特征:
1.可以通过直接操作名字,而改变变量
2.可以将操作的结果带出函数
语法:
Type &引用名=被引用变量
实例如下:
int main(int argC, Char* argv[]) { int nValue = 0x12345678; int &refValue = nValue; refValue = 0x55555555; printf("%p,%p\r\n", nValue, refValue); return 0; }
从运行结果可看出,nValue和regValue两个的值都变了。
但是如果不使用引用,则结果如下:
引用也可作为形参,兼具指针和普通变量的优点:
#inClude"stdafx.h" #inClude <string> #inClude <iostream> using namespaCe std; void SwapValue(int& a, int& b) { int iTemp = a; a = b; b = iTemp; } int main(int argC, Char* argv[]) { int nValue1 = 0x11111111; int nValue2 = 0x55555555; printf("nValue1:%p,nValue2:%p\r\n", nValue1, nValue2); SwapValue(nValue1, nValue2); printf("nValue1:%p,nValue2:%p\r\n", nValue1, nValue2); return 0; }
输出结果:
引用的本质
引用于指针的联系?
引用的本质是指针,但是从C++角度看,还是有不同的
1.引用需要初始化
2.引用指向的变量,初始化时就决定了,不能被更改。
上图运行完后,nValue1和nValue2的值都将变为nValue2的值。
3.有二级指针,但是没有二级引用。
Const
中文“常量”,被 Const 修饰的变量,不能被改变:
int main(int argC, Char* argv[])
{
Const int nValue1 = 0x11111111;
nValue1 = 0x55555555;
return 0;
}
运行结果:
C 和 C++中const的区别:
两者中机制不同。C中的机制是编译前的语法检查,C++中进行常量替换。
Const 修饰指针变量
因为指针变量比较特殊,他自己是一个变量,他又指向另外一个变量,所以Const修饰指针时,就会有两种情况:
1.Const 限制指针本身不变(地址不变)
2.Const 限制指针指向的变量值不变。
实例如下:
int main(int argC, Char* argv[]) { int nValue1 = 0x11111111; int nValue2 = 0; Const int* pValue1 = &nValue1;//指针指向的地址可以改变,指向变量的值不能改变 int Const* pValue2 = &nValue1;//同上 int* Const pValue3 = &nValue1;//指向的地址不能变,指向变量的值可以变 Const int* Const pValue4 = &nValue1;//指向的地址和值都不可以改变 //pValue1 = &nValue2; //*pValue1 = 0x22222222; //pValue2 = &nValue2; //*pValue2 = 0x22222222; //pValue3 = &nValue2; //*pValue3 = 0x22222222; pValue4 = &nValue2; *pValue4 = 0x22222222; return 0; }
编译结果:
结论并记忆:
若是 Const 在*的左边,即是限制指针指向的变量值不变。若是Const 在*的右边,即是限制指针本身不变(地址不变)
函数重载
C 中,是不予许同名不同参的函数存在的。即有的函数逻辑一样,名字一样,但是参数不同,所以不能同时存在。
int myfun(int x, int y) { return x + y; } float myfun(float x, float y) { return x + y; }
以上函数是不能同时存在的,因为C语言中不予许同名函数存在。
而实际上,在编译时,编译器会根据传的参数类型不同,而去找到对应的函数。
所以,C++中做了改进,允许同名不同参数的函数存在,这个特性就是:函数重载
函数与函数构成重载有三个条件;
1.函数名相同
2.参数个数、类型、顺序不同
3.只有返回值不同,不构成重载,如下:
#inClude"stdafx.h" #inClude <string> #inClude <iostream> using namespaCe std; int myfun(int x, int y) { return x + y; } float myfun(int x, int y) { return x + y; } int main(int argC, Char* argv[]) { myfun(100, 200); myfun(3.12f, 2.44f); return 0; }
##函数重载的原理
名称粉碎机制。
C 中,编译器将源码编译成 obj 文件后,其中的函数名字和原来的函数名字是没有区别的。
但是 C++中,生成的 obj 中会将函数的参数、个数等考虑在内,形成一个复杂的名字。
这是因为编译器做了这些额外的工作,使得链接器可以根据不同的粉碎后的名词,定位到不同的函数。这个原理和我们手工给函数加后缀是一样的。
命名空间
C 时代:为了解决一个大项目中,在没有约定的前提下,不同的人的代码中出现相同名的情况,采用约定前缀的方法。
C++中,引入了名称空间来解决该问题。
语法:
namespaCe 空间名字{ //定义函数变量 }
实例:
#inClude <iostream> using namespaCe std; int g_nValue = 100; int g_nValue = 200; int main(int argC, Char* argv[]) { return 0; }
以上是编译不通过的。
更改:
#inClude"stdafx.h" #inClude <string> #inClude <iostream> using namespaCe std; namespaCe zhao{ int g_nValue = 100; } namespaCe qian{ int g_nValue = 200; } int main(int argC, Char* argv[]) { return 0; }
如上就可以编译通过了
作用域运算符::
为了配合名称空间的使用,C++中发明了作用域运算符(::)。
命名空间原理:
仍是名称粉碎机制
using namespace
有的名称经常被用到,频繁使用作用域运算符不方便,所以就引入了using namespace 关键字,该方法是在编译器需要的时候,自动添加作用域。
使用: