C++入门基础

本文介绍了C++中的命名空间、输入输出、缺省参数、函数重载、内联函数、引用、nullptr、auto关键字以及范围for等概念,展示了如何避免命名冲突,提高代码效率和可读性。
摘要由CSDN通过智能技术生成

一.命名空间

1.命名空间的定义

在学习命名空间之前先看一下以下C语言代码,在C语言中,rand是生成随机数的函数

而代码中我们希望rand是整型并赋值为10,这样就会重定义,编译不通过

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存
在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,
避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题

2.定义命名空间

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}
中即为命名空间的成员

1.正常的命名空间定义

命名空间中直接定义变量/函数/类型等

2.命名空间可以嵌套

3.相同名称的命名空间

同一工程多个相同名称的命名空间,编译器会整合到同一个命名空间中

3.命名空间的使用

这里有一个命名空间N,我们如何使用命名空间里的变量以及函数呢?

以下有三种方式:

1.加命名空间名称及作用域限定符::

2.使用using将命名空间中某个成员引入

3.使用using namespace 命名空间名称引入

4.编译默认查找顺序

1.当前局部域

2.全局作用域 / 展开的命名空间

二.C++输入和输出

我们来看下C++是如何来实现打印 " hello world" 的

说明:
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream >头文件中。
3. <<是流插入运算符,>>是流提取运算符。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C++的输入输出可以自动识别变量类型
5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,
这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。后面我们还有有
一个章节更深入的学习IO流用法及原理。

三.缺省参数

1.缺省参数的概念

(1)缺省参数是声明或定义函数时函数的参数指定一个缺省值

(2)在调用该函数时,如果没有指定实参则采用该形参的缺省值否则使用指定的实参

2缺省函数的分类

1.全缺省参数

2.半缺省参数

3.注意事项

1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能函数声明和定义中同时出现

3. 缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)

四.函数重载

1.引言

在学习函数重载之前,先看一段C语言代码

观察代码发现,三个代码分别求 int double char 类型的最大值

特点:1.都执行相同的一般性操作    2.都返回两个形参中的最大值

这反映了程序设计环境的一种局限性: 在同一个域中出现的名字必须指向一个唯实体(函数体) 

用户来说,只有一种操作,即计算两参数的最大值

但是对于程序员来说带来一个实际问题:必须要记住每一个函数的名字

函数重载把程序员从这种词汇复杂性中解放出来

2.函数重载概念

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

3.函数重载举例

1.参数类型不同

2.参数个数不同

4.C++支持函数重载的原理--名字修饰

为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们
可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标
文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么
怎么办呢?
2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就
会到b.o的符号表中找Add的地址,然后链接到一起
3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的
函数名修饰规则

由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,所以我们在Linux系统下,g++环境演示C语言和C++编译器编译后的差异

采用C语言编译器编译后结果:

结论:编译完成后,函数名字的修饰没有发生改变

采用C++编译器编译后结果:

结论:函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中

看完了linux下g++的修饰规则,我们再来看一下windows下的修饰规则

我们以int N::C::func(int)这个函数签名来猜测VisualC++的名称修饰规则(当然,你只须大概了解这个修饰规则就可以了)。修饰后名字由“?”开头,接着是函数名由“@”符号结尾的函数名:后面跟着由“@”结尾的类名“C”和名称空间“N”,再一个“@”表示函数的名称空间结束第一个“A”表示函数调用类型为“cdecl”,接着是函数的参数类型及返回值,由“@”结束,最后由“Z”结尾。可以看到函数名、参数的类型和名称空间都被加入了修饰后名称,这样编译器和链接器就可以区别同名但不同参数类型或名字空间的函数,而不会导致 link的时候函数多重定义

讲到这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修
饰规则来区分,只要参数不同,修饰出来的名字就不一样
,就支持了重载

五.引用

1.引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间

类型& 引用变量名(对象名) = 引用实体

以下代码可以看出,引用和引用的对象公用一块空间

2.引用特性

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

3.引用权限

引用过程中,只能权限平移或者权限缩小,不可以权限放大

看下面代码:

x可读可写,y是x的别名,y可读可写,属于权限的平移   

z是x的别名,const int & z = x ,被const修饰,z不可写可读,属于权限的缩小

m被const修饰,不可写可读,int& n = m;这样写m就可以通过n修改值,属于权限放大,不可以

在类型转换的时候,会产生临时变量,临时变量具有常性,可读不可写

int& a = d;属于权限放大

4.传值,传引用效率比较

我们看以下代码:

结构体A里面放了一个数组a,四万字节

TestFunc1传值接收,每次调用都需要传参

TestFunc2传引用接收,每次调用不需要传参

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低,传引用可以提高效率

5.引用和指针

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间

但是在底层实现上实际是有空间的,因为引用是按照指针方式来实现的

我们看以下代码:分别用引用和指针改变a的数值

转到汇编:发现指针和引用在底层的操作是一样的

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

六.内联函数

1.引言

普通函数频繁调用的过程消耗栈空间和时间

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。

一个 C/C++程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条,这个链条的起点是 main(),终点也是 main()。当 main() 调用完了所有的函数,它会返回一个值(例如return 0;)来结束自己的生命,从而结束整个程序。

函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。

如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。

为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数

2.概念

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

指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字

举例:日常写代码会频繁用到交换函数swap,这时候就可以用内联函数inline提高效率

3.特性

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用

缺陷:可能会使目标文件变大        优势:少了调用开销,提高程序运行效率。


2. inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性

3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到

4.宏定义

宏的优缺点?
优点:
1.增强代码的复用性
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。

七.auto关键字

1.类型别名思考

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错

类似以下代码:

std::map<std::string, std::string>::iterator 是一个类型,但是该类型太长了,特别容
易写错;在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义

2.auto简介

auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto
的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编
译期会将auto替换为变量实际的类型

3.auto使用细则

1.auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但使用auto声明引用类型时必须加&

2.在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际只对第一个变量进行推导,然后用推导出来的类型定义其他变量

3.auto不能作为函数的参数

4.auto不能直接用来声明数组

4.范围for

1.范围for的语法

在C++98中如果要遍历一个数组,可以按照以下方式进行:

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因
此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范
围内用于迭代的变量,第二部分则表示被迭代的范围

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

2.范围for的使用条件

1.for循环迭代的范围必须是确定的

2.迭代的对象要实现++和==的操作

八.指针空值nullptr

1.C++98中的指针空值

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现
不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下
方式对其进行初始化

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何
种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void
*)0

注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值