1、初始化列表方案
1.1、整型变量初始化可以直接用 { } 赋值
1.2、数组、指针同样可以使用初始化列表方案
1.3、注意:一般不用 int a( ) 来定义、初始化a变量,因为在这里会和定义函数int a( ) 形参冲突;而int b{ } 是可以使用的,会自动初始化b为0
———————————————————————————————————————————
2、C++的输入输出
2.1、使用cin.gee(str,20)可以给字符串中直接输入空格,换行符“ 回车键 ”作为结束标记
2.2、使用” # “作为结束标记
———————————————————————————————————————————
3、const和指针(重点)
3.1、const在C和C++中的区别:
const在c中以变量性为主;在c++中以常量性为主
在.c文件中,[ ]中的数值必须是常量、或者是宏替换的常量、或者是枚举常量
C++中const修饰的这个n要保持常性
在内存中,*p变成了100,n变成了100;但是编译的时候n不复存在,遇到n就给替换成了字面常量5,然后再赋值给b
3.2、const 放在 * 号前,修饰的是指向性,也就是修饰*P,不能解引用,但是P自身保存的地址可以改变
比如:
int a = 10 ,b = 20;
const int* P = &a;
这里我们可以让P = &b,但是不能通过*P = 100 来改变a的
3.3、const放在* 号后,修饰的是变量名(变量自身具有了常性),那P的指向就可以改变,可以使用解引用,但是P保存的地址是不能改变的
const放在* 号后,不能更改地址
const放在* 号后,可以通过*p修改a变量的值
3.4、* 号的前后都有const,那P、*P都不能被改变
3.5、安全性
3.6、指针之间的相互赋值
———————————————————————————————————————————
4、补充:
1、c语言中,scanf()函数的安全性判断解决(scanf_s()):
将目标的大小由程序员指定
2、判断是编译器是C++还是C,通过编译器自带的宏 _cplusplus 来判断
———————————————————————————————————————————
5、引用
引用就是“别名”的概念
5.1、特点:
1、定义“引用”,必须要进行初始化
2、没有空引用
3、引用没有二级引用
5.2、引用作为形参代替指针
在c语言中,一般情况下,我们要交换两个变量的值,需要借助交换函数swpe(),通过“传指针,解引用”达到交换的目的
但是在c++里面,我们可以通过“引用”来实现交换,在swpe()函数这里,我们将函数的参数设为要交换的变量的别名。例如要交换变量int a,int b的值,swpe()函数的参数,我们分别可以设为int & x,int & y,函数内部直接借助临时变量交换x、y的值即可
5.3、引用和const
const 必须放在 & 的左边,表示常引用(放在&右边会修饰变量名,而被系统忽略,所以不会修饰&)
由于是常引用,所以c只能读取a的值,不能修改a的值,但是可以通过改变b的值来修改a的值大小
注意:
对于下方常变量a,我们不能通过普通引用x去引用a,否则就是说明我们可以通过修改普通引用x来改变常变量a的值,所以要用常引用y去引用a
形参的几种形式:
5.4、const引用是万能引用:
const引用可以引用:变量、常变量、字面常量
const引用在引用字面常量的时候实际上是:在底层定义了临时量tmp,接着让Z调用tmp,而不是直接引用字面常量10。引用字面常量10的时候,实际上引用的是tmp,10没有地址空间
5.5、右值引用概念
左值:在C11标准的概念中,有取地址的能力,比如变量a可以取地址,和变量类型无关
右值:不可以取地址,比如字面常量(int & x = 10,这个就是错误的,但是const int & x = 10 么问题)
左值引用
右值引用
int && r = 10;这个是所谓的右值引用
和常引用的相同点:都是在底层定义一个tmp变量保存右值10
不同点:我们的常引用不能 +=10来改变字面常量10,但是右值引用可以让r+=10,是对临时量tmp += 10
5.6、其他引用形式
5.6.1、引用和数组
1、别名影响数组元素的值
2、引用整个数组。格式:int(&cr)[5] = ar;
5.6.2、数组我们称为组合类型:由类型和大小组成
int &ar [5]
不能编译通过,数组元素给到下标,就得开辟空间,但是这里&取别名是不需要开辟空间的
int (&br)[5]
给整个数取别名,例如:int (&br)[5] = ar;这里br是数组ar的别名,这个数组是int类型,有5个元素
int *P [5]
指针数组:从右向左,有5个元素,p的每一个元素是整型类型的指针
int (*s)[5]
数组指针:指针s要指向元素个数为5的整型数组
5.6.3、引用和指针
我们可以对一个指针变量ip进行引用,即int *& s = ip;
理解:对指针ip先取别名 &s,然后这个别名是int * 类型
代码分析:
1、s是对指针ip的引用,即别名。s = &b 改变s的指向,也就是改变ip的指向;
2、const int *& s1 = ip,是对指针ip进行取别名,并且s1设置为常性指针,那就是说不能通过 *s1 来改变a的地址,但是 *ip是可以的
3、int* const& s2 = ip,是对指针ip取别名,const修饰变量名ip,可以解引用,不能修改地址
5.7指针和引用的区别:
5.7.1、指针和引用的区别
1-9从语法角度上看区别,10是汇编层面上的区别
1、从语法规则上讲,指针变量存储某个实例(变量或对象)的地址,而引用是某个实例的别名。
2、程序为指针变量分配内存区域(指针分配4字节);而不为引用分配内存区域(仅仅取外号)
3、解引用是指针使用时要在前加“ * ”;引用可以直接使用
4***、指针变量的值(指针的指向)可以发生改变,存储不同实例(比如变量a、b)的地址;引用在定义时就被初始化(也就是它就指定是某个变量的外号,这个变量的地址是确定的),之后无法改变(不能是其他实例的引用)。
5、指针变量的值可以为空(NULL,nullptr);但是没有空引用(引用前必须定义好某个变量)。
6、指针变量作为形参时需要测试它的合法性(判空NULL);引用不需要判空;
7、对指针变量使用“ sizeof ”得到的是指针变量的大小(4字节),对引用变量使用“ sizeo f”得到的是变量的大小(由变量类型决定)。
8、理论上指针的级数没有限制;但引用只有一级。即不存在引用的引用,但可以有指针的指针(二级指针、三级指针)
9、++引用(直接反应到实体) 和 ++指针的效果不一样。对引用的操作直接反应到所引用的实体(变量或对象);
知识引申: 数组中,(*p)++是:先取p对应的值,然后对p的值再加1;*p++是:先取i、p对应的值,然后p地址加1个单元格
10、从汇编层次理解引用与指针变量的区别:(从汇编层面看,引用就是指针,是一个自身为常性的指针,不能指向其他实例,可以改变其值)
从汇编代码上看一下引用和指针区别
int* ip = &a;
int& x = a;
都是通过lea取a的地址,然后给到eax,然后通过eax给到对应的变量
5.7.2、指针和引用的相同点:
绝不可以对函数中的局部变量以引用或指针方式返回,因为函数调用(会先开辟空间)结束之后,栈帧回收,那局部变量就会失效,但是值还存在,有可能打印的时候,这个值是可以正常打印的,但是如果我们再次调用某个函数,对这个栈帧进行操作,那原先这个位置的值就会被清理掉
要返回地址的时候,此标识符名字对应的实体生存期不受函数生存期影响,就可以返回地址。比如:定义的全局变量、或者static int a静态变量(在 .data区)、或者是由指针进入形参,指针返回、或者以引用进、以引用出的方
5.8、如何使用引用
对于自定义类型,传参数时候,优先使用传引用方式(分析结构体占用内存);
对于内置类型,函数调用传参数的时候,优先使用传值型(从底层汇编代码分析)
———————————————————————————————————————————
6、inline函数
6.1、小点注意:
cin输入的时候,以' \n ' 结尾,但是不把' \n ' 保存到变量内部,如果这个cin是在循环里面的话,那就会循环输入,永远不会结束
get.cin()这个代码以' \n ' 结尾,但是会最终会把最后面' \n ' 保存到变量内部,所以可以作为判断结束的条件
6.2、内联函数的理解:
当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。
inline是一种以空间换时间的做法,省去调用函数额开销。但当函数体的代码过长或者是递归函数即便加上inline关键字,也不会在调用点以内联展开该函数。简单来说就是函数代码长度小,就直接在主函数内展开,否则过大就调动函数
内联函数的定义和声明一定要在同一文件中,不能将声明和定义分别写在两个文件里面
6.3、什么时候建议使用内联函数?
如果函数的执行开销小于开栈清栈开销(函数体较小),使用inline处理效率高。
如果函数的执行开销大于开栈清栈开销,使用普通函数方式处理。
6.4、内联函数与宏定义区别:
(1)内联函数在编译时展开,带参的宏在预编译时展开。
(2)内联函数直接嵌入到目标代码中,带参的宏是简单的做文本替换。
(3)内联函数有类型检测、语法判断等功能,宏只是替换。
———————————————————————————————————————————
7、缺省函数
C++可以使用缺省函数,但是C语言不可以
函数在定义的时候,可以指定形参的值为默认值;函数调用的时候,如果给到全部的参数,那就以给到的实参传递给形参,如果没有给实参,那形参就按照默认的值来进行操作
缺省函数特点:
1、函数的缺省值有可能是常量值、int()、int{ }、也有可能是函数调用,还有可能是类型的调用
2、函数缺省值在给定的时候,要从右向左去给到(但是给实参的时候要从左向右给到)
3、在多文件情况下,一般我们都只给函数的声明中给到缺省参数的默认值就行,而不能在函数定义中给到(多文件:比如有 .h头文件、.cpp函数实现文件、main.cpp测试文件)
4、缺省参数也可以使用指针形式,但是不能让形参作为另外一个形参的默认值
5、函数在声明的时候可以省略”形参名“
———————————————————————————————————————————
8、函数重载
8.1、函数重载的概念
在C++中可以为两个或两个以上的函数提供相同的函数名称,只要参数类型不同,或参数类型相同而参数的个数不同,称为函数重载。
在C语言中函数名称不能一样,如果一样会发生函数名的重定义
在C++中识别函数,不仅通过函数名识别,还要通过参数表(参数类型,参数个数)进行识别,参数名称不作为识别依据,例如两个类似的函数在定义的时候,其中一个函数的参数有包含默认值,这个时候是不作为识别依据的。
8.2函数重载的各类变化
8.2.1、返回值不能作为识别依据。
两个参数名、参数相同,但是返回类型不同,会被标记为编译错误:函数的重复声明。
8.2.2、参数名不能作为识别依据。
参数表进行比较的时候,和参数名称无关
8.2.3、如果在两个函数的参数表中,只有缺省实参不同,也不能作为函数重载的依据。
也就是第二个声明被视为第一个的重复声明。
8.2.4、typedef也不能作为函数重载的依据
8.2.5、C++中const(常性)、volatile(易变关键字)
当一个形参类型有const或volatile 修饰时,如果形参是按值传递方式定义,那么在识别函数声明是否相同时,并不考虑const和 volatile 修饰符,也就是说const或volatile在值传递的时候并不能作为函数重载的依据
但是如果按照指针方式定义,或者按照引用方式定义的话,那么进行const、volatile参数传递的话是可以作为函数重载依据的
8.2.6、函数调用的二义性
第二个func()函数含有缺省参数,所以主函数中func(12)在调用的时候,上面两个函数都可以进行调用,所以就会产生二义性,所以重载函数的时候,不要轻易使用缺省值,以免函数调用产生二义性
8.2.7const和引用&,在函数重载中的匹配规则:
普通变量会优先调用形参是普通指针的函数,如果没有普通指针(int* p)的函数,那就退而求其次找到形参为常性指针(const int* p)的函数;而常变量只能找形参为常性指针的函数
普通变量会优先调用形参是普通引用的函数,如果没有普通引用(int& p)的函数,那就退而求其次找到形参为常引用(const int& p)的函数;而常变量只能找形参为常引用的函数
还有要注意以下这种方式1、3也是不能函数重载的:
8.3、c语言为什么不能进行函数重载,而C++可以进行函数重载?(待整理)
答案:关于名字粉碎技术,在编译中会对函数名进行另外的修饰
名字粉碎(名字修饰)——“C” 或者 “C++” 函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。
C++在做名字修饰的时候,会把参数作为函数名的一部分。c只是单纯的依据函数名
C语言的名字修饰规则非常简单,_cdecl是C/C++的缺省调用方式,调用约定函数名字前面添加了下划线前缀。
C++的名字修饰足够复杂
auto的原理就是根据后面的值,来自己推测前面的类型是什么。
auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。
如何判断编译器是C还是C++?
在编译器下有个宏:_cplusplus,检测这个宏存在与否
——————————————————————————————————————————————————————————————————————————————————————
9、函数模板
模板类型推演:
是对类型重命名的规则,不是简单的替换;
特别是当T表示指针类型,例如 int* ,的时候,T x,y;是正确的;如果是替换的话就是 int* x,y;此时x是指针类型,y是整型,所以此处并非是替换,而是类型重命名
编译时:模板函数是产生代码的代码
10、名字空间域:namespace
11、new / delete
12、C11部分特性
12.1、类型推导
1、auto关键字
C++11引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能方便地获取复杂的类型,而且还能简化书写,提高编码效率。
c11中auto称为类型指示符(type-specifier)。
在c语言中auto是存储类型指示符。
auto类型推导:auto定义的变量,可以根据初始化的值,在编译时推导出变量名的类型。
可当作函数模板去使用:
2、decltype关键字
上一节所讲的auto,用于通过一个表达式在编译时确定待定义的变量类型,auto所修饰的变量必须被初始化,编译器需要通过初始化来确定 auto 所代表的类型,即必须要定义变量。若仅希望得到类型,而不需要(或不能)定义变量的时候应该怎么办呢?
C++11新增了decltype关键字,用来在编译时推导出一个表达或的类型。
它的语法格式如下∶
decltype (exp)
其中,exp表示一个表达式(expression) 。
从格式上来看,decltype很像sizeof-—用来推导表达式类型大小的操作符。类似于sizeof,
不计算值,只取类型;函数也是,只取返回值类型,不调动函数
12.2、基于范围的for循环
根据auto,自动推导容器中的元素类型:
12.3、nullptr