【C++】基础知识

C++是什么:

用较为简单易懂的话来解释,就是在C的基础上改进的新语言,+的意思就是PLUS,因为是基于C语言的改进,所以引入了一些新的概念、语法,同时它也兼容C。

关键字

跟C语言一样,C++同样有一些平时常用的单词,例如int char之类的,这里我放一张表,记不住也没有关系,之后学了才会有更深的理解。

下面是表:

命名空间

这里要提到的一点是命名空间,相较于其他关键字,namespace出现的频率会高一些,在C++程序里的前几排,总有这么一行代码:

using namespace std;

要想理解这句话,就应该从什么是namespace说起,先来看一下官方的解释:

C/C++ 中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化 ,以 避免命名冲突或名字 污染 namespace 关键字的出现就是针对这种问题的。
如何理解?
举个例子,现在,我有两个变量都叫A,那我运行程序的时候程序肯定会报错,因为它不知道要用的是哪个A。

这种情况下,就可以用到namespace。

以下面情况举个例子。
namespace N1
{

    int a;
        
}

namespace N2
{
    int a1 = 10;
    
}

namespace N3
{

    int c;
namespace N2
{
    int a1 = 10;
    
}
}

两种都是可以的,namespace的作用就是将花括号里面的内容单独开辟一块空间出来,这块空间是不会与主程序产生冲突。

函数、结构体...这些都是可以放在命名空间里面的。

命名空间是可以嵌套使用的,但如果命名空间的名字相同,比如说这块空间是N1,另一块空间也是N1,那最后处理的时候它们就会合并到一起去。

命名空间就像新开辟了一块空间,命名空间里面的内容都局限于这一块空间中

如果要调用这块命名空间的内容,那就有两种办法,第一,是指定一块空间让系统在那里面去找。

举个例子

这里就是很典型的例子,可以看到主程序里面并没有a1,但是我在它的左边加了两个双引号,并在双引号左边加了一个N2。

这行代码就是让程序在N2这块空间里面找一个叫a1的数字。

第二种办法是直接释放这块命名空间,第一种办法每次都只是去找其中一个元素,使用的次数一多,每次都加双引号和命名空间名是件很麻烦的事,所以为了解决这个问题可以一次将这里面的元素释放出来,这就需要在前面加一句

using namespace xxx;

这里又有两种用法,第一种是:

using N2::a1

 

意思是将N2里面的a1元素释放出来,如果嵌套的很多,那就要加更多的引号。
第二种:一次释放完全
使用办法:

 如果是一次释放完全的话,那就应该在前面加上namespace,表明释放的是一块命名空间而不是单个元素。

那之前提到的:

using namespace std;

的意思就是将std里面的全部东西释放出来,经常使用的函数等...

C++输入&输出

就好像新学C语言是先在屏幕上打印一个“hello world”一样,C++也先从这里开始比较好。

先输出吧,C++的输出跟C的区别这里就能看出来,假设要打印一句:hello world

 cout表示要输出东西,随后用两个左箭头,再用双引号将文本括起来,最后再用两个左箭头隔开,endl是换行的意思。

这里有几点要注意:

1. 使用 cout 标准输出 ( 控制台 ) cin 标准输入 ( 键盘 ) 时,必须 包含 < iostream > 头文件 以及 std 标准命名空间。
注意:早期标准库将所有功能在全局域中实现,声明在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std 命名空间下,为了和 C 头文件区分,也为了正确使用命名空间,规定 C++ 头文件不带.h ;旧编译器 (vc 6.0) 中还支持 <iostream.h> 格式,后续编译器已不支持,因此 推荐 使用 <iostream>+std 的方式。
2. 使用 C++ 输入输出更方便,不需增加数据格式控制,比如:整形 --%d ,字符 --%c

头文件肯定是要包含的,std命名空间要用记得释放。

这里要说的是第二点,C++输出的时候不要增加类型,编译器会自动识别,这里将输入也连起来:

创建三个变量,输入有要用cin,随后顺序输入,中间用向右的箭头连接,表示下一个输入的是它。

输入的时候跟刚刚一样,只不过中间的空格需要手打比较麻烦,不过这里可以看到没有提示类型它也可以直接输出,相较C舒服多了。

缺省参数

这里是跟C差异较大的地方,在C里面,函数声明有几个参数,那你传过去就要几个参数,但是在C++里面,就不是这样的。

先从概念说起:

缺省参数是 声明或定义函数时 为函数的 参数指定一个默认值 。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

举个例子:

这里就是调用了缺省参数,我参数里面接受类型里面定义了一个变量a,但是我传参数的时候缺没有传a的值过去,那C++就默认用函数参数里面定义的那个值,这里是 10。

但是如果我传来一个参数过去:

这里如果我传了一个2作为参数过去,那函数调用就会优先使用我传过去的参数。

函数重载 

在C里面,函数名是不可以冲突的,但是在C++里面,就不会有这个问题,举个例子:

这里就是重载函数,它函数名一样,但是函数的参数不一样,根据我传过去的参数不同,系统会调用对应的函数。

然后,再来看一下对于重载的说明:

函数重载 : 是函数的一种特殊情况, C++ 允许在 同一作用域中 声明几个功能类似 的同名函数 ,这些同名函数的 形参列表 ( 参数个数 或 类型 或 顺序 ) 必须不同 ,常用来处理实现功能类似数据类型不同的问题。

short Add(short left, short right) 
{
 return left+right; 
}

int Add(short left, short right) 
{
 return left+right; 
}

这算吗?

它的函数名不同吗?

它的参数个数不同吗?

它的参数类型不一样吗?

但是它只有返回值不一样,所以它并不算重载参数。

重载参数的实现

这里就要从底层实现开始说,当程序开始运行的时候,它会经过几个阶段:

1、预处理:头文件展开、宏的替换、条件编译、去掉注释

这里会生成 .I文件

2、编译:检查语法,生成汇编代码

这里生成 .s 文件

3、汇编:将汇编代码转换为二进制的机器码

这里生成 .o文件

4、连接:开始找要调用的函数地址,然后将其连接在一起

这里生成  .out文件

那函数重载,C/C++就是在连接部分不一样,因为我还不会Linux,所以这里先说一下结论,之后再将详细过程写出来。

总之,C在连接过程中会很简单的将函数名对应起来,如果相同那它就会产生冲突

但是C++不是,它的函数名对应是这样的:_Z+函数名+参数首字母

假设是这样的一个函数:

C里面应该是两个fun函数,但是C++是这样的:_Z3i 和 _Z3d

因为函数名的参数不一样,它的函数名就不一样,所以在找的时候就不会有冲突问题。

引用

 这个我个人认为是指针的改版,举个例子:

我现在要交换两个数,如果是指针,那应该这么写:

如果是用引用,那它应该是这样的

同样可以达成想要的效果,那再来看看引用的解释:

引用 不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
类型 & 引用变量名 ( 对象名 ) = 引用实体;

这里要注意两点,引用类型应该和引用实体是一个类型(但是如果不是一个类型,加一个const也是可以的)。

引用特性

1. 引用在 定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体

这是可行的,但是如果引用没有初始化,那就会报错:

 同时,还可以:

引用引用(套娃)

但是引用只有一个对象,不能再引用其他对象,这里就要注意下。

同时,这里还要注意一点:

引用只能将权限缩而不能放大:

举个例子:

在这里就是一个很明显的例子,const修饰的对象是不能被修改的,而你引用的权限是可以修改,所以就会报错。

如果要引用,那在引用的时候就应该也带上const 

 引用还可以作为返回值,作为返回值返回的是那一块空间的地址。

但是这种是错误的示范,因为函数返回的是n的别名(本质还是地址),但是n是一个局部变量,出了函数就会销毁,在销毁之后再去访问就会出现问题——越界。

再看这样一个代码,这里拿ret来接收了它的返回值,但是之后又调用了这个函数,在之后打印结果是?

本意是 3.但是引用返回的是c的地址,在1+2后又对这一块空间进行修改:

所以它就出错了。

如果再次看结果,这就是一个随机值了,因为这一块空间已经归还给了系统,再去看就被刷新了。

 引用的效率问题

值和引用的作为返回值类型的性能比较

如果是这样的一个程序:

明明c的寿命周期已经到了,那为什么还能返回?

因为系统会将这个值暂存下来,可以理解为临时拷贝一份,那既然是临时拷贝,这效率就可以直说。

它没有引用块,假设这个数据很大,有一百万个数据,一千万个数据 ,那拷贝就要耗时间,而引用是直接返回地址,所以会更快。

相较于指针,引用和指针应该是一个级别。

引用和指针的区别

在概念上,指针是一块地址,引用是变量的别名,它们占用空间就有区别

但底层实际方面,引用跟指针实现方式很像,也是要占用一块空间的。

1. 引用 在定义时 必须初始化 ,指针没有要求
2. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型
实体
3. 没有 NULL 引用 ,但有 NULL 指针
4. sizeof 中含义不同 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占
4 个字节 )
5. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全

内联函数 
 

当使用函数的时候,会有这么一种情况,就是:如果这个函数很短,但是需要用的地方很多,比方说实现一个A+B的函数,有没有什么办法可以解决这个问题?

当然,宏,那宏是怎么写的呢?

这里给你十秒,想一个宏实现A+B。

想出来了吗?

-------- 

因为宏直接替换,不好理解而且不方便查看过程,很容易出错,那这里就引入一个概念叫内联函数。

这里先看一下内联函数的定义:

inline 修饰 的函数叫做内联函数, 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数压栈的开销,内联函数提升程序运行的效率。

要理解这句话,要先理解什么是函数栈帧,每调用一个函数,都会在内存中开辟一块空间,然后压栈。

但如果是使用内联函数,那么程序运行就会直接在函数调用地方展开,不压栈,这样效率会更高。

但是如果这个函数很长,再用内联函数的话,每次调用都展开未免也有点太长了,所以编译器在执行的时候会先判断这个函数内容多不多,如果这个函数内容很多,它会不执行内联,如果很短,它会先替换。

那么多少代码算长的?

10行左右。

如果你想,所有函数前面都可以加一个inline,让编译器自己判断。

--------

内联函数的特性 

1. inline 是一种 以空间换时间 的做法,省去调用函数额开销。所以 代码很长 或者有 循环 / 递归 的函数不适宜使用作为内联函数。
2. inline 对于编译器而言只是一个建议 ,编译器会自动优化,如果定义为 inline 的函数体内有循环 / 递归等等,编译器优化时会忽略掉内联。
3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。

这里重点要了解第三条,如果分离,展开的时候就找不到函数本体,这里看一个例子:

 

在Code.h里面我声明了一个内联函数,但是我没有定义,而是在:

Code.h里面定义了这个函数,如果现在要用这个函数,会发生什么?

 

无法解析的外部符号,这里就可以直接理解为程序运行的时候找不到这个函数,但是我明明声明了这个函数,为什么还是找不到?

因为inline被展开之后它没有定义,只是一个声明,既然只有一个声明那它怎么可能调用呢?

这里要注意一下。

这里把宏的优缺点说一下:

优点:
1. 增强代码的复用性。
2. 提高性能。
缺点:
1. 不方便调试宏。(因为预编译阶段进行了替换)
2. 导致代码可读性差,可维护性差,容易误用。
3. 没有类型安全的检查 。
C++ 有哪些技术替代宏
1. 常量定义 换用 const
2. 函数定义 换用内联函数

这里第一条有在《EffectiveC++》的条款里面提到:

EffectiveC++ 条款02:尽量以const,enum,inline替换#define

 这里看图:

可以看到,maxa已经有值了,但是无法识别出MAX。 

为了解决这种类似问题,就可以用const——无法修改的变量和enum枚举常量来代替。

auto关键字

当定义一个变量的时候,应该是这样定义的:

int i = 1;

float j = 1.1;

char k = 'A';

auto的作用就是,它会根据后面变量的数值来给类型:

在早期 C/C++ auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量 ,但遗憾的是一直没有人去使用它,大家可思考下为什么?
C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得

 

 如果用auto,是怎么样的?

可以看到基本是一样的。

但如果开始并未赋值,就会报错:

因为auto是根据后面的值来判断类型,如果不给值那它就无法判断。

如果用auto来定义指针,会发生什么?

可以看到,如果是auto + &,那auto就会默认是int*

如果是第二种,auto后面已经带*了,那auto还是int*。

如果是引用,那就应该是这样:

 还要注意,如果一行定义多个变量,那这些变量都应该是一个类型:

 同时,它不能作为函数参数:

还是那句,它是根据后面的值来给类型的,并且必须初始化。

同时,不能声明数组:

 auto在for循环内部

以往,遍历数组是这样的

现在,可以用auto:

 

 它会自己遍历每个数打印,那能不能直接修改呢?

可以看到,第一个遍历已经修改了里面的值,但是到了第二个遍历可以看到还是原来的值。

这是因为这里面的e是一份临时拷贝而不是对应的那个值,如果通过这个修改值,那有什么方法?

别名。

可以看下图:

这样就可以了。

指针空值nullptr 

这个是后来的规定,大致意思:

C语言有以下两种办法指定空指针:

int*p = 0;

int* pp = NULL;

这两种都是制定为空指针,但是在C++(11)的标准里面,不算规范写法,是因为这两种写法的等价的:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

这是网上找到的C头文件,可以看到这里是直接讲 NULL替换为 0,或者是void类型的指针。

这里有办法可以看看:

这是一个重载函数,但区别是一个是指针一个是整形,前面两个传参是 0 和NULL,看右边可以看出来它们调用的都是整形的函数,这里就可以看出来NULL跟0是一样的,而如果在传参时将NULL强转为int*,那它才会调用第二个函数。

 那规范的空指针写法是什么样的?

如图,nullptr是规范的空指针写法,传过去的也是一个指针。

这里有几点注意:

nullptr是关键字,不需要头文件

C++11中,sizeof(nullptr) sizeof((void*)0)所占的字节数相同 

能用nullptr,就用nullptr,这样比较规范

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值