C++入门
C++兼容C语言
入门就是补充一些C语言不支持的(给C语言补坑),为类和对象打基础
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;;
return 0;
}
命名空间
C语言没有解决命名重复的问题
#include <stdlib.h>
int rand = 0;
//发生命名重复,库里有rand函数,造成命名重复
命名空间域
命名空间的定义
域的概念在C++中非常重要。定义命名空间,需要使用namespace
关键字,后面跟命名空间的名字,然后接一对{}
即可。{}
中即为命名空间的成员。
namespace tl
{
int rand = 0;
}
C++命名空间可以定义变量、函数、类型。命名空间可以嵌套定义(无限层)。⚠️同一个工程中允许存在多个相同的命名空间,编译器最后会合成同一个命名空间。
同一个域不能定义相同变量,不同的域可以。
一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
命名空间的使用
主要介绍::
域作用限定符号
int a = 0;//全局域
int main()
{
int a = 1;//局部域
printf("%d\n",a);//就近原则打印a = 1
printf("%d\n",::a);//空白就是全局域限定
}
命名空间的使用有三种方式
- 加入命名空间及作用域限定符
int main()
{
printf("%d\n", tl::a);
}
- 使用
using
将命名空间中某个成员引入
using tl::b
int main()
{
printf("%d\n", tl::a);
printf("%d\n", b);
}
- 使用
using namespace
命名空间名称引入
using namespcae std;
有结构体时,注意符号位置⚠️struct tl::TreeNode node;
访问嵌套的命名空间 struct sql::tl::TreeNdoe node;
要避免出现和库里常见重复的命名,比如rand
加入using namespace tl;
后,编译器先在全局域去找,如果还没有就会带展开的tl域中去找。
using namespace std
是C++标准库的命名空间;把C++库里的东西暴露出来,展开了就有重复的风险
命名空间使用原则
- 项目中,尽量不要
using namespace std;
- 日常练习用
using namespace std;
- 项目,指定名空间访问+展开
缺省参数
缺省参数是备胎,在声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
全缺省参数
void TestFun(int a = 10, int b = 20, int c = 30);
全缺省:函数的每一个参数都有“备胎”
TestFunc(1)
这个是给a
的
TestFunc(1, 2)
是给a
和b
的
TestFunc(,1,)
不可以❌
传参一定是从左到右 想传右边的参数一定要传左边的参数(想传第三个参数必须要传前两个参数)
半缺省参数
void TestFun(int a, int b = 20, int c = 30);
半缺省:函数的部分参数有“备胎”,但是半缺省参数必须从右往左依次来给,不能间隔给。
注意⚠️:
- 半缺省参数必须从右往左缺省,不能间隔
- 缺省参数不能在函数声明和定义中同时出现。所以只给声明不给定义。
- 缺省值必须是常量或者全局变量。
- C语言不支持。
函数重载
函数重载概念
C语言不允许同名函数出现,这就很不方便。比如在写两数的加法函数时,需要定义很多不同名函数来满足不同参数类型的需要。
C++支持同名函数,但是函数形参列表(参数个数 或 类型 或 类型顺序)不同。
返回值不同不能用于判定函数重载,因为调用时无法区分。
意义
<<
流运算符自动识别类型*本质上是函数重载
函数重载的意义:“像用同一个函数一样”
C++支持函数重载的原理——名字修饰(name Mangling)
为什么Cpp支持函数重载,C语言不支持?
首先了解一下编译链接过程:
编译连接的过程:
- 预处理:头文件展开、宏替换、条件编译、去掉注释 fun.i,main.i
- 编译:语法检查,生成汇编代码 func.s main.s
- 汇编:把汇编代码转换为二进制机器码 生成目标文件func.o main.o
- 链接:生成可执行程序Linux: a.out win:xxx.exe
链接时,.o的目标文件合并到一起,其次还需要找一些只给声明函数变量地址。比如链接时面对Add
函数,链接器会使用哪个名字去找呢?这要看不同编译器的函数名修饰规则。
由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,因此参考使用Linux下的gcc
和g++
。
gcc的函数修饰后名字不变,而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】
结论:Cpp加入函数名修饰规则(参数不同,修饰出来的名字不同),C语言直接用函数名填入汇编符号表里(函数名没有经过处理)
**就是C++他在链接的时候 符号表里的函数名会根据参数名命名 C只通过函数名命名。**通过这里就理解了C语言没办法支持重载,因为同名函数没有办法区分,而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
C++和C可以互相调用
Cpp的程序能不能调用C的程序,C程序能不能调用Cpp程序?交叉调用
结论:都是可以的,但是需要做一些处理
C++调用C
Cpp调用C:用extern C
包含头文件,告诉Cpp的编译器,这里面的这些函数是C的库实现的,你用C的规则去链接查找他们(只能是函数链接,其它不行)
extern "C"
{
#include "../StackC/Stack.h"
}
C 调用C++
C调用C++ extern
和_cplusplus
条件编译
在.c
文件里不要用extern "C"
,因为C编译器不认识extern "C"
在Stack.h里用如下方法修饰:
#ifdef _cplusplus
extern "C"
{
#endif // _cplusplus
void StackInit(ST* ps);
void StackDestroy(ST* ps);
//...
#ifdef _cplusplus
}
#endif // _cplusplus
如果是C编译,则不会执行extern "C"
;C++编译器编译,则会执行extern "C"
。
引用
引用的概念
引用:大佬觉得指针有时候太复杂了,就搞了一个别名 引用就是给**变量取别名**
int& b = a;
引用不开辟新空间,两个共用一个空间
引用的特性
- 引用在定义时必须初始化
int& b;
❌ 否则我都不知道你是哪个别名 - 一个变量可以有多个引用,引用也可以引用*;一个变量可以有好几个外号,外号的外号*
- 引用一旦引用一个实体,再不能引用其它实体(引用从一而终) ⚠️与指针不同,指针可以改
例题:删除空指针是无害的,不能删除引用。☑️
解答:空指针没有任何指向,删除无害,引用是别名。删除引用就是删除真实对象。
常引用
//C++打印变量类型
int a = 10;
cout << typeid(a).name() << endl;
//打印结果为int
const int a = 0;
const
修饰的变量只可以读不可以写
引用针对const权限平移和缩小,不能放大,对指针也适用
//权限不能放大
const int c = 20;
//int& d = c;❌
const int& d = c;
//权限可以缩小
int e = 30;
const int& f = e;
//强制类型转换或者隐式类型转换会出现临时变量
//临时变量具有常性
int ii = 1;
double dd = ii;
//double& rdd = ii;//临时变量具有常性,权限被放大
const double& rdd = ii;//const只可读
类型转换,并不会改变原变量类型。中间都会产生一个临时变量
函数传参不用引用和指针时,是拷贝,不受权限放大的影响。以下的的传值是可以的。
void Func(int a)
{
printf("%d\n", a);
}
int main()
{
const int a = 0;
Func(a);
return 0;
}
建议用const,有非常强的接受度。如果使用引用传参,函数内如果不改变n
,那么建议尽量用const
引用传参
void func2(const int& n);
C++中,指针和引用用途基本是相似的;指针在C++列表的场景中(链式结构),引用替代不了;其它地方引用基本可以替代指针。
- 引用在定义时必须初始化
- C++的引用不可以更改指向
指针更强大,更危险,更复杂;引用相对局限一些,更安全,更简单
语法角度而言,引用没有开空间,指针开了4或8个字节。从底层实现的角度而言(需要看懂汇编代码),引用底层是用指针实现的。(拼多多和金鹰买鞋)
引用的场景
1.做参数
a.输出型参数(指针也能做)
b.大对象传参,提高效率
我们现在认为,从语法角度上引用不开空间
2.作返回值
a. 输出型返回对象,调用者可以修改返回对象
b. 减少拷贝,提高效率
如何用引用做返回值是正确的?
传值返回:统一生成一个返回对象拷贝作为函数调用返回值(无论变量在栈还是在静态区储存),不会智能地识别
传引用返回的语法含义:返回返回对象n的别名
总结:
- 什么情况下不能用引用返回:如果出了函数作用域,返回对象就销毁了。那么一定不能用引用返回,一定要用传值返回。
- 出了作用域,对象还在可以用引用返回。
关键看变量出了作用域还在不在
例题:引用传值,指针传地址❌
解答:引用表面好像是传值,其本质也是传地址,只是这个工作由编译器来做,所以错误