C++
是 C语言
的超集,是一门在 C语言
基础上发展起来的语言,C++
很强大,如今 C++
已是一个多重范型编程语言,主要包含四部分:C
、Object-Oriented C++
、Template C++
和STL
,因此我们一般将 C++
看作一个语言联邦,显然 C++
的内容很丰富,也比较难学,但当我们掌握后,它将称为一把利刃
C++
摘得 TIOBE 2022 年度编程语言桂冠
先简单了解下 C++
的起源
C++祖师爷—本贾尼·斯特劳斯特卢普
C语言
是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言
则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生 总结:C语言
无法满足大型软件的开发需求
无所谓,祖师爷会出手
1979年,贝尔实验室的本贾尼等人试图分析
UNIX
内核的时候,试图将内核模块化,于是在C语言
的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes
1982年,本贾尼博士在
C语言
的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言
的渊源关系,命名为C++
因此:C++
是基于C语言
而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
在本贾尼博士的不断修修改改下,一门新的编程语言 C++
就诞生了
1998年,
C++
标准第一个版本发布,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写C++
标准库,引入了STL
(标准模板库)2011年,
C++
11标准发布,增加了许多特性,使得C++
更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等2020年,
C++
引入了许多新的特性,比如:模块(Modules)、协程(Coroutines)、范围(Ranges)、概念(Constraints)等重大特性,还有对已有特性的更新:比如Lambda支持模板、范围for支持初始化等
由此可见,C++
是一门更新周期长、内容丰富的编程语言,如今大多数公司主要实用标准为 C++98
和 C++11
,因此我们现阶段还不需要掌握最新标准内容,从 C++
入门基础开始,稳扎稳打,争取学好 C++
C++
兼容 C语言
,因此 C语言
中的所有32个关键字在 C++
中均可以使用,当然 C++
在此基础上新增了31个关键字,使得 C++
中的关键字数达到了63,当然这么多关键字现在没必要全部看懂,在学习后面知识时会用到
江湖规矩,学习 C++
首先少不了 Hello World!
#include<iostream> //IO流头文件
using namespace std; //全局展开std命名空间
int main()
{
// cout 是输出函数
// << 流插入运算符,配合 cout 使用
// endl 换行符,相当于 \n
cout << "Hello World!" << endl;
return 0;
}
向世界打个招呼后,我们就可以正式开始 C++
的修行之路了
命名空间是我们接触的第一个 C++
特性,当然其他高级语言也支持
背景
C语言
时,我们自己定义的名字可能和库函数起冲突,或者在同一个项目组中,多人定义的名字起冲突此时我们只有改名字这一条路可选,显然祖师爷本贾尼对于这种方法很不满, 于是他想出来这种解决方法
变量/函数
限定使用,即使存在两个同名变量,只要相应空间找对了,也不会起冲突#include<iostream> //IO流头文件
using namespace std; //全局展开std命名空间
//命名空间
namespace AA
{
int num = 10;
}
namespace BB
{
int num = 24;
}
int main()
{
//不同命名空间内的同名 变量/函数 不冲突
cout << AA::num << endl;
cout << BB::num << endl;
return 0;
}
此时我们就能看懂下面这行代码了
using namespace std; //全局展开std命名空间
它的作用是展开 std
这个命名空间,即库函数,只有展开后,我们才能正常使用 cout
这种展开方式叫做 全局展开
,除了 全局展开
外,还有 部分展开
和 域作用限定符展开
全局展开
using namespace 命名空间; //全局展开覆盖范围为整个程序
//比如
using namespace AA; //全局展开AA这个命名空间,可以在任意位置使用AA中的变量/函数
部分展开
using 命名空间::待使用变量/函数;
//比如
using AA::num; //只展开AA中的变量num,此时可在任意位置使用AA中的变量num
域作用限定符
//需要使用时
cout << 命名空间::待使用变量/函数 << endl; //需要使用时展开
//比如
std::cout << AA::num << std::endl; //这个就是使用时展开
//注意:假设 :: 左边为空,如 ::num 这种情况,会去全局范围内查找变量
//num,如果没有找到,就会报错
三种方式各有好坏,使用场景有所不同
全局展开
的方式,因为不受其他人干扰,也不会干扰其他人部分展开
+ 域作用限定符
,频繁使用的对象通过 部分展开
,使用频率较少的对象直接使用 域作用限定符
就行了注意:
祖师爷在 C++
中设计了缺省参数这个概念,使得函数在没有参数传递时也可以按其他方式运行
缺省参数
#include<iostream> //IO流头文件
using namespace std; //全局展开std命名空间
//在函数声明时给形参设定初始值
void print(int val = 999)
{
if (val == 999)
cout << "缺省参数已启用 val 值为:";
else
cout << "缺省参数未启用 val 值为:";
cout << val << endl;
}
int main()
{
print(100);
print(); //设有缺省参数的函数,可以不传参数
return 0;
}
缺省参数的出现使得函数运行多了一种可能,实际运用场景如:在 栈
初始化时,设定缺省参数值为4,即默认大小为4,假若用户不传参数,则按4来初始化 栈
大小;若传递了参数,则按实参初始化 栈
大小
缺省参数很实用,但也要慎用
缺省参数有很多使用规则:
1、只允许从右到左依次连续缺省,不得出现跳跃缺省
//正确用法
void test(int a, int b = 2, int c = 3); //从右至左,连续缺省
//错误用法
void test(int a = 1, int b, int c); //非从右至左缺省
void test(int a, int b = 2, int c); //跳跃缺省,非法
2、调用时,实参依次从左往右传递
//正确用法
test(4,5,6); //不启用缺省值,a = 4,b = 5, c = 6
test(5,6); //启用缺省参数 c,a = 5, b = 6, c = 3
//错误用法
test(); //此时必须传一个参数,因为参数 a 不是缺省参数
test(1,2,3,4); //参数传多了
3、声明和定义中不能同时出现缺省参数,只能在声明中出现
//test.h
//声明时缺省
void test(int a = 10);
//test.c
//定义时不必再缺省
void test(int a)
{
cout << a << endl;
}
4、C语言不支持缺省参数
void test(int a = 10); //C语言实现会报错
2023.4.8
更新
5、缺省参数值只能为全局变量或静态变量
C语言
不允许两个函数名字相同,比如函数 Add
只能适用于一种数据类型,在 C++
中支持函数重载,即在参数不同(包括类型不同或顺序不同)的前提下,允许同时存在多个同名函数
//C语言
int Add(int x, int y);
double Add(double x, double y); //此时会报错,两个函数名冲突
//C++
int Add(int x, int y);
double Add(double x, double y); //正常编译,即使函数名都是Add,但在C++中编译器能分清两者
原因: C++中引入了新的函数名修饰规则,比如对于两个Add函数来说,Linux中会分别修饰为 _3Addii 与 _3Adddd,显然两者是不同的;而对于C语言来说两个函数名修饰后都为 Add
我们可以在 Linux
环境下,通过指令 objdump -S 可执行程序
查看函数名修饰情况
函数名修饰后,后序并入符号表,链接时只要函数修饰名不冲突,就可以正常链接
Linux
中对于函数名的修饰规则比较简单,而 Windows
中则比较复杂,如在 VS
中,上述函数名修饰为 ?Add@@YAHHH@Z
过于复杂了
下面是重载的各种情况
//假设存在函数 func
void func(int* pa, int len); //修饰为 _4ZfuncPii 指针需加P
//正确的重载情况,只要修饰后的函数名不冲突,就能构成重载
void func(int& pa, int len); //修饰为 _4ZfuncRii 引用需加R
int func(int len, int* pa); //修饰为 _4ZfunciPi
char* func(int* pa); //修饰为 _4ZfuncPi
//错误的重载情况
int func(int* pb, int n); //修饰为 _4ZfuncPii 冲突
void testc(int a, int b); //修饰为 _4Ztestcii
void testc(int b, int a); //修饰为 _4Ztestcii 冲突
注意: 返回值不纳入函数名修饰中,假若加入,函数调用时就会出现混乱,因此返回值不同并不构成函数重载
引用是一个很好用的工具,它有指针指向同一块数据的能力,同时它不像指针那样危险、复杂,换句话说,引用是指针改进版,在后续学习中,有 80% 的场景都会使用引用而非指针
int a = 10;
int* pa = &a; //指针
int& ra = a; //引用
引用的底层实现仍然是指针
引用相当于给变量取别名
比如在我们日常生活中,马铃薯/山药蛋/洋芋/洋番芋/薯仔/Potato等等都表示 土豆,不同地区叫法不同,换句话说,土豆 在全国各地有很多个引用,只要表示对了,都是指 土豆
上面代码段中的
ra
与a
都表示同一块空间,而*pa
和a
也表示同一块空间;可以简单把引用理解为一个智能版指针,会自动解引用,使用起来更方便
引用有很多使用特性,即使用规范,使得引用更加安全
引用b
代表 引用a
时,实际上就是在代表 引用a
所代表的变量 a
char a = 'A';
char b = 'B';
//1、引用必须初始化
char& ra = a; //正确
char& rx; //错误,必须初始化
//2、一个变量可以有多个引用
char& ra = a;
char& rra = a;
char& rrra = a; //没有问题,一个变量允许存在多个引用
//3、引用无法改变指向
char& ra = a;
char& ra = b; //错误,引用一旦确立后,就无法再改变其指向
ra = b; //这个没问题,实际结果为 a = 'B' 即将 b 的内容赋值给 a
//4、不存在多级引用
char& ra = a;
char&& b = ra; //非法,不存在多级引用
char& b = ra; //合法,实际结果为 char& b = a;
注意: 引用不能像指针那样随意使用,引用也不存在指针多级指向的功能
比如下图这种情况对于引用来说是不存在的
对于指针和引用来说,存在权限问题,因为指针和引用具有直接修改原数据的能力
众所周知,对于程序来说,存在几个区域:栈、堆、静态区等等,我们使用的常量位于数据段或代码段中,常量具有可读不可修改的特性,当我们使用普通指针或引用指向常量数据时,会引发错误
int main()
{
const int a = 10; //此时a为常变量,具有常量属性
const int* pa = &a; //正常
const int& ra = a; //正常
return 0;
}
解决方法也很简单,将指针或引用改为只读权限,就能正常指向常量了(权限平移)
引用主要有以下几个使用场景
1、做参数
void swap(int& ra, int& rb)
{
//有了引用之后,不需要再解引用,也能达到指针的效果
int tmp = ra;
ra = rb;
rb = tmp;
}
2、做返回值
#include<iostream>
using namespace std;
int arr[10] = {0}; //数组为全局变量
int& getVal(int pos)
{
//返回数组指定位置的值
return arr[pos];
}
int main()
{
for(int i = 0; i < 10; i++)
{
//借助引用,可以直接通过返回值修改数组中的值
getVal(i) = i * 10;
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
当引用做返回值时,接收到的变量就是函数返回时的本体,比如全局数组 arr
,此时对返回值做出修改,就是在改变数组 arr
引用返回很强大,但也不能随便使用,引用返回一般用于生命周期较长的变量,即函数结束后不被销毁的变量,如果使用生命周期短的变量作为引用返回值,那么结果是未定义的
int& func(int n)
{
int val = n;
return val; //结果未定义
}
//val是函数 func 中的局部变量,当函数结束后,变量就被销毁了
//此时可能得到正确的结果(编译器未清理),也可能得到错误的结果(编译器已清理)
//因此说结果是未定义的
//可以看到下图中相同语句出现两种结果
引用返回原理:
之前我们一直都是走的临时变量那条路,现在有了引用后,在使用生命周期较长的变量时,就可以考虑使用引用返回来提高效率
性能对比 | 1w数据量 | 10w数据量 |
---|---|---|
普通值返回 | 14ms | 1753ms |
引用返回 | 0ms | 1ms |
引用是比较重要的特性,需要小结一下:
以后涉及需要改变原变量值时,优先考虑使用引用,特殊场景下,仍然需要使用指针
引用与指针互不冲突,两者都有自己适用的领域,比如在实现链表时,必须使用指针,因为引用无法改变指向,而链表需要频繁的进行改链操作
内联函数主要是为了替代宏函数,因为宏函数存在很多坑,并且在某些场景下使用复杂
#define ADD(x, y) ((x) + (y)) //通过宏函数实现ADD,比较复杂、麻烦
除了使用复杂外,宏还存在以下缺点:
在书籍《Effective C++》 中,作者建议
const
和 enum
替换宏定义的常量inline
替换宏函数所谓内联函数就是在函数实现前加上 inline
修饰,此时函数会被编译器标记为内联函数
//此时的 Add 函数就是一个内联函数
inline int Add(int x, int y)
{
return x + y;
}
内联函数特点:
Debug
模式下,函数不会进行替换,可以进行调试Realse
模式下,函数会像宏函数一样展开,提高程序运行速度内联函数可以全面替代宏,当然使用时也需要注意
这个是 C++11
中的新特性,auto
关键字能直接识别目标变量类型,然后自动转换为相应类型
int a = 10;
int* b = &a;
auto aa = a; //此时 aa 为 int
auto bb = b; //此时 bb 为 int*
除了自动识别外,我们还可以指定转化类型
int a = 10;
auto* pa = a; //指定 pa 为 int*
auto& ra = a; //指定 pa 为 int&
在后期学习中,某个变量类型可能非常长,此时可以利用 auto
自动识别类型
auto
支持一行声明多个变量,当然类型得统一
auto a = 1, b = 2, c = 3; //合法,类型统一
auto a = 1, b = 2.2; //非法,类型不统一
注意: auto 不能用于数组,auto 也不能当作参数类型
这个也是 C++11
中的新特性,是一个语法糖,范围 for
循环拥有自动拷贝、自动判断范围、自动结束等特点,使用起来很方便
#include<iostream>
using namespace std;
int main()
{
int arr[] = {1, 2, 3, 4, 5};
//范围 for 循环
for(auto val : arr)
{
cout << val << " ";
}
cout << endl;
return 0;
}
范围 for
配上 auto
自动识别类型,写出来的循环很简单,在加上引用,使得我们可以轻而易举的给数组赋值
#include<iostream>
using namespace std;
int main()
{
int arr[] = {1, 2, 3, 4, 5};
//范围 for 循环
//配合引用修改原数组值
for(auto& val : arr)
{
val *= 2;
cout << val << " ";
}
cout << endl;
return 0;
}
注意: 范围 for 遍历数组时,数组大小必须确定,迭代的对象要实现 ++ 和 == 的操作
这个是 C++11
中新增的补丁,因为在设计 C++
时,指针空值 NULL
出了点问题,NULL
可能被编译器直接识别为 0
而非 void*
#include<iostream>
using namespace std;
void func(int)
{
cout << "参数为整型 0" << endl;
}
void func(void*)
{
cout << "参数为指针 void*" << endl;
}
int main()
{
func(0);
func(NULL);
return 0;
}
可以看到,NULL
并没有被识别为指针空值,因此委员会推出了 nullptr
这个补丁,专门用于给指针置空
注意:
nullptr
是作为关键字加入的,不需要头文件nullptr
和 void*
的大小一致nullptr
而非 NULL
auto、范围for、nullptr 这些都是 C++11 中的新特性,较老的编译器可能不支持
以上就是关于 C++
入门基础的全部内容了,我们从 C++
的诞生开始,认识了 C++
为弥补 C语言
缺陷所做出的改动,也学习了 C++
中的各种新特性,如 引用
、内联
、auto
等等;C++
很强大,学习周期很长,但我相信锲而不舍,金石可镂,再高的山峰也有人成功登顶,C++
的修行之路才刚刚开始,我们已充满信心
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!