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 关键字,该方法是在编译器需要的时候,自动添加作用域。
使用:
图片说明