C++入门教学

一.命名空间

1.引言

我们都知道C++是在C语言的基础上发展起来的,因此C++几乎可以完全兼容C,即99%的C语言程序都可以在C++上运行成功,同时C++在C语言的基础上增加了很多特性来解决C语言的弊端,使我们使用更方便.

C语言的一大弊端就是命名冲突,这个冲突可能是我们的命名与库函数命名的冲突,也可能是我们的命名与其他人命名的冲突,在一个大型工程里面,有上百个文件,很容易发生命名冲突.所以C++引入了命名空间来解决命名冲突的问题.

命名空间namespace

2.用法

这样我们就定义了一个名为My的命名空间.{ }里面的就是命名空间域.在命名空间域里面不仅可以定义变量,还可以定义函数,还可以定义结构体这些类型.

那么我们该怎么样访问命名空间里的成员呢?

访问命名空间一共有三种方法

第一种

我们就要用到域作用限定符"::".例如My::rand 这时候我们就可以访问My中的rand了.同理My::Add.但是要注意结构体的访问.这个方法的好处是不用担心命名冲突,但是有个弊端就是每次都得加上::,对于一些使用频繁的变量就会很繁琐.


这里补充一下域的知识:

域可分为局部域  全局域  命名空间域 还有后面的类域

命名空间域还是全局的,不影响生命周期,只是隔离了名字,影响了编译器的查找.

第二种

这个就是将命名空间展开

using namespace My;

这样就可以直接使用命名空间里的成员.

展开并不是放在了全局域里,但是因为编辑器的查找规则,依然会和全局域发生命名冲突

当两个命名空间同时展开并且其中含有同名变量也会发生命名冲突

所以尽可能不要轻易展开命名空间

但是在我们日常练习,代码量少的情况下还是可以展开的.

第三种

指定展开命名空间里的某一个

例如using namespace My::rand

这样就可以方便的使用rand而不把其他成员放出来造成命名冲突了

对于一些使用频繁的成员这个方法很有效

3.本质

 命名空间解决命名冲突的本质是影响了编译器的查找

编译器的查找规则:

1.先在当前所在的局部域进行查找

2.再在全局域里面进行查找(默认情况下不会去命名空间域里查找)

3.在展开的命名空间里查找

这就不难解释为什么不同的域可以定义同名的变量,函数,类型等,同一个域里不能定义同名变量的原因了.

因为编译器的查找顺序.在局部域查完没有后再在全局域和展开的命名空间里查找.

(特别注意全局域和展开的命名空间是同一优先级,而不是先在全局域找再去展开的命名空间里找)

不同的域有同名变量但是因为编译器的查找顺序不发生冲突

相同的域有同名变量编译器找到多个同名变量不知道到底是哪个导致歧义.

4.补充

首先命名空间是可以嵌套的,在一个命名空间里定义另一个命名空间.与结构体里定义结构体类似.

其次就是多个文件可以定义同名的命名空间,同名的命名空间会进行合并

例如在test1文件里定义的命名空间My会和test2文件里定义的同名命名空间My合在一起.要注意这些命名空间合并在一起后可能会发生命名冲突.

二.标准输入输出流

C语言中输入输出有scanf和printf

而C++规定了更为方便的cout和cin函数

其头文件为<iostream>

注意我这里明明包含了头文件为什么还会显示cout未被定义呢?

这是因为c++的库为了避免与用户的函数发生命名冲突,将库封装在名为std的命名空间里

在我们日常练习中,我们可以直接展开std

1.cout 

<<这个符号有了新的含义,流插入

优点:

1.可以自动识别输入的类型,比如我输入的变量i 直接输出了20 不用printf里指定"%d"的打印格式

2.可以连续输入并且之间可以任意加东西 例如加上的换行 空格等.

注:endl是end line 即是换行"\n"

cout在后期我们还会学习其他特性,这里就先不讲述了

2.cin

>>流提取

优点:

1.不用指定格式

2.不用取地址

三.缺省参数

在形参后面直接给一个值,与形参类型匹配且一般为常数

1.全缺省

特别注意必须要从左到右按顺序传参,不能跳着传 例如func(4, ,5)就不行

2.半缺省

 必须要从右往左缺省     这与传参时必须要从左往右传相对应  主要是为了防止歧义

3.注意

缺省参数 声明和定义不能同时给,也是为了防止函数声明和定义的缺省参数值不同造成歧义

缺省参数推荐在函数声明时给出

四.函数重载

1.引言

C语言是不允许同名函数存在,但是C++可以支持同名函数的存在,这就是C++的函数重组

2.函数重载的条件

1.在同一个域内

2.同名函数

3.函数的参数列表不同(参数类型,个数以及参数类型的顺序不同),注意不看形参名称和函数返回类型(因为函数的返回值不是必须的)

3.函数重载的原因

为什么C语言不支持函数重载而C++支持函数重载?

C语言 链接时直接使用函数名去查找  

1.简述

C++ 链接时直接使用修饰后的函数名去查找

所以C语言不支持函数重载,C++支持函数重载

2.详细

是否支持函数重载涉及到编译链接的过程

函数有一堆要执行的指令,函数的地址是函数第一句指令的地址,函数的地址在函数定义时生成,函数的声明并不会生成函数的地址

test.cpp中只有函数声明,但是在编译时时可以通过的,因为语法检查是匹配的,此时没有函数的地址,而函数的地址在stack.cpp中.test.cpp->test.o,stack.cpp->stack.o,当test.o与stack.o链接合并在一起时才有了函数的地址.

链接时要用函数的名字去stack.o的符号表里查找

C语言是直接用函数名字查找,所以当两个函数同名时就不知道到底是哪个函数了,因此不支持函数重载

C++会用函数的参数列表修饰函数,用修饰完后的函数名称去查找,因此支持重载

C++的修饰函数名称  不同编译器的修饰不同

以vs为例

int  H代替   char  D代替

int int        HH 

char char      DD

int char    HD

char   int  DH

参数类型不同,数量不同,顺序不同,修饰就不同,因此可以查找到不会冲突,因而支持函数重载   

五.引用

1.概念

引用不是新定义一个变量,而是给已经存在的变量取一个别名,从语法层面来讲,编译器不会为引用变量开辟内存空间,它和引用的变量公用一块空间.

b是a的别名,

d是a的别名而不是b的别名.

一定要注意谁是本名,其他的都是本名的别名而不是别名的别名.

在C语言中,我们会认为Swap函数是传值调用并不会交换a和b的值

但在C++中,我们可以看到Swap函数的参数x是a的别名,y是b的别名,x和y交换,也就是a和b交换.

从这里我们可以看出以引用作为函数参数与指针作为参数的效果是一样的.

2.引用的特性

1.引用在定义时必须进行初始化

2.一个变量可以有多个引用,也就是多个别名

3.引用一旦引用一个实体,再也不能引用其他实体,即初始化之后便不能把该引用变为其他变量的引用

3.常引用

1.权限的放大

可以看到这里报错了,是因为n是只能读取不能被修改的,而m作为n的别名却既可以读取又可以修改,这就属于权限的放大,是不可以的

2.权限的平移和缩小

第一个两个都是只读,属于权限的平移,第二个由原来的能读能写取了个只读的别名,属于权限的缩小.权限的平移和缩小都是允许的

3.权限的对象

权限的放大平移和缩小都是对于引用和指针而言,普通变量的拷贝不存在权限的放大平移和缩小

4.补充

类型转换和表达式都要注意权限的放大问题

d要从double类型转换为int,会产生临时变量,存放d的整数部分,再由这个临时变量拷贝到i中

而该临时变量是常变量,即const int,所以如果引用中不加入const就属于权限的放大报错

第七行也出现了报错也是同理,即权限的放大

4.引用与指针的对比

1.引用在概念上是定义一个变量的别名,指针是存储一个变量的地址

2.引用在定义时必须要初始化,指针不要求初始化

3.引用在初始化引用一个实体后,不能再引用其他实体,指针可以

4.没有NULL引用,有NULL指针

5.sizeof中的含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节数

6.引用自加即引用的实体+1,指针自加是指针向后偏移一个类型的大小

7.没有多级引用,但有多级指针

8.访问实体的方式不同,引用是编译器自己处理,而指针是显示引用,即解引用*或者[ ]

相比之下,引用要比指针安全一些,但是也会存在隐患

六.内联函数

1.引言

对于一些频繁调用的小函数,为了减少函数调用时创建栈帧的消耗

C语言引入了宏函数 但宏函数较为复杂且极易出错

例如这个两数相加的宏函数

1.为什么不能加分号?   注(int a;;;;不会报错) 

2.为什么要加外面的括号?

3.为什么要加里面的括号?

如果宏函数里面加了;在后面写程序就要格外注意在ADD函数后面加东西了

加外面括号是为了防止出现比+优先级高的打乱运算顺序

加里面括号是为了防止出现比+优先级低的

总之C语言的宏函数要注意很多东西并且比较复杂

为此C++引入了内联函数

2.概念

以inline修饰的函数叫做内联函数

1.inline是以时间换取空间,编译器将函数当成内联函数进行处理,编译阶段会用函数体替换函数调用

缺陷:可能会使目标文件变大

优势:少了函数调用的开销,提高程序运行的效率

2.inline对于编译器只是一个建议,不同编译器对于inline的实现机制可能不同

一般建议将函数规模较小,不是递归且频繁调用的函数采用inline修饰,否则编译器将忽略inline的特性,即内联只是向编译器发出一个请求,编译器是可以忽略这个请求的,例如编译器不可能展开一个1000行代码的内联函数的

因为如果函数有1000行,有10000个位置调用

展开   1000*10000行代码

不展开  1000+10000行代码

虽然是拿空间换取时间的做法,但耗费的空间也不能过大

3.补充

inline不支持将声明和定义分离,分离会导致链接错误,因为编译器一旦将函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,链接就会找不到该函数,也不能在其他文件中调用,故一般都是直接在源文件中定义内联函数的

也可以直接在.h头文件里定义内联函数

1.可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数

inline函数会在调用的地方展开,所以符号表中不会有inline函数的符号名,不存在链接冲突。

2.使用inline关键字的函数会被编译器在调用处展开

不一定,因为inline只是一种建议,需要看此函数是否能够成为内联函数

3.头文件中可以包含inline函数的声明

不可以,内联函数不支持声明和变量分离

宏的优点:增强代码的复用性   提高程序的性能

               缺点:不方便调试宏(预编译阶段就进行了替换)  导致代码可读性差,可维护性差,容易误用

                         没有安全类型的检查

C++替代宏的技术

1.常量定义 换用const enum

2.短小函数定义  换用内联函数

七.关键字--auto

通过等号右边初始化的值自动推导变量类型

后期一些类型写起来很长,auto比typedef要方便很多,从而简化代码

1.typeid

typeid可以根据变量推导出变量的类型

2.auto的使用注意事项

1.auto后加*

auto后不加*不管什么类型都可以

加了*则指定必须是指针,但什么类型的指针不管

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

当在同一行定义多个变量时,这些变量类型必须相同,否则会报错

因为编译器实际上是对第一个类型进行推导,然后用推导出的类型定义其他变量

3.auto不能推导的场景

1.不能作为函数参数

2.不能用来定义数组

3.基于auto的范围for       遍历数组

:分成两部分,前面是范围内用于迭代的变量,后面是迭代的范围

自动取数组a中的值赋值给e 自动++,自动判断结束

注:是把a中的值拷贝给e,e相当于临时变量,e的修改并不会影响a

若是想要改变a的话需要加个引用 auto&e

与普通循环一样,也可以用continue跳过和break跳出循环

只能从前往后遍历,不能从中间或者倒叙遍历数组

数组传参传的是首元素的地址,接收下来a就是个指针,但是范围for遍历对象只能是数组,指针不行所以会报错

8.指针空值nullptr

在C语言中我们通常使用NULL作为指针空值

但NULL实际上是一个宏 

#define NULL 0

NULL被定为字面常量 0或者无类型指针(void*)的常量

但是这是有缺陷的

这是两个重载函数,整形打印int 指针打印int*,可NULL和0一样都打印int   后面将NULL强制转化为int *类型后才被当作指针处理'

而C++引入的nullptr直接就被当做指针处理

这代表nullptr作为空指针要比NULL安全.这也就是为什么C++要把nullptr作为关键字引入来代替NULL做空指针

  • 52
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值