一、前言
最近刚好有空,趁这段时刻,温习一下C++言语
,进一步夯实根底,为今后的底层开发
、音视频开发
、跨渠道开发
、算法
等方向的进一步学习埋下伏笔
咱们在上一篇文章中,现已充分阐明,C++言语是对C的扩展,树立在对C言语常识把握的根底上学习C++是事半功倍的
假设你对C言语现已淡忘,或许没有学过C言语,且一时半会没有思路怎样筛选可靠的C言语学习资料,能够学习我的这几篇文章:
1. C言语中心常识
- 01-温习C言语中心常识|总述
- 02-温习C言语中心常识|根本语法、数据类型、变量、常量、存储类、根本句子(判别句子、循环句子、go to句子)和运算
- 03-温习C言语中心常识|函数、效果域规矩、数组、枚举、字符与字符串、指针
- 04-温习C言语中心常识|结构体、共用体、位域、输入&输出、文件读写
- 05-温习C言语中心常识|预处理、头文件、强制类型转化、过错处理、递归、内存办理
二、语法须知、::
效果域、姓名操控
1. 语法须知
- C++的源文件扩展名是:cpp(c plus plus的简称)
- C++程序的进口是main函数(函数即办法,一个意思)
- C++彻底兼容C言语的语法,很久以前,C++叫做C with classes
2. ::
效果域运算符
- 一般状况下,假设有两个同名变量
- 一个是
大局变量
,另一个是部分变量
- 那么
部分变量
在其效果域内具有较高的优先权,它将屏蔽大局变量。 - 示例代码:
//大局变量 int a = 10; void test(){ //部分变量 int a = 20; //大局a被隐藏 cout << "a:" << a << endl; }
- 程序的输出成果是a:20
- 在test函数的输出句子中,运用的变量a是test函数内界说的部分变量,因而输出的成果为部分变量a的值
- 一个是
-
效果域运算符
::
能够用来处理部分变量
与大局变量
的重名问题 - 示例代码:
//大局变量 int a = 10; //1. 部分变量和大局变量同名 void test(){ int a = 20; //打印部分变量a cout << "部分变量a:" << a << endl; //打印大局变量a cout << "大局变量a:" << ::a << endl; }
- 个例子能够看出,效果域运算符能够用来处理部分变量与大局变量的重名问题
- 即在部分变量的效果域内,可用
::
对被屏蔽的同名的大局变量进行拜访
3. 姓名操控
- 创立姓名 是程序设计进程中一项最根本的活动,当一个项目很大时,它会不行避免地包含大量姓名
- C++答应咱们对姓名的发生和姓名的可见性进行操控
- 咱们之前在学习C言语能够经过
static
关键字来使得姓名只得在本编译单元
内可见 - 在C++中咱们将经过一种经过
命名空间
来操控对姓名的拜访
- 咱们之前在学习C言语能够经过
3.1 C++命名空间(namespace)
- 在C++中,称号(name)能够是
- 符号常量
- 变量
- 函数
- 结构
- 枚举
- 类和方针等等。
- ……
命名抵触
- 工程越大,称号相互抵触性的或许性越大
- 别的运用多个厂商的类库时,也或许导致称号抵触。
namespace
- 为了避免,在大规模程序的设计中,以及在程序员运用各种各样的C++库时,这些标识符的命名发生抵触
- 规范
C++
引进关键字namespace
(命名空间/姓名空间/称号空间),能够更好地操控标识符的效果域
3.2 命名空间运用语法
- 创立一个命名空间:
namespace A{ int a = 10; } namespace B{ int a = 20; } void test(){ cout << "A::a : " << A::a << endl; cout << "B::a : " << B::a << endl; }
- 命名空间只能大局范围内界说(以下过错写法)
void test(){ namespace A{ int a = 10; } namespace B{ int a = 20; } cout << "A::a : " << A::a << endl; cout << "B::a : " << B::a << endl; }
- 命名空间可嵌套命名空间
namespace A{ int a = 10; namespace B{ int a = 20; } } void test(){ cout << "A::a : " << A::a << endl; cout << "A::B::a : " << A::B::a << endl; }
- 命名空间是开放的
即能够随时把新的成员参加已有的命名空间中namespace A{ int a = 10; } namespace A{ void func(){ cout << "hello namespace!" << endl; } } void test(){ cout << "A::a : " << A::a << endl; A::func(); }
- 声明和完结可分离
-
声明
#pragma once namespace MySpace{ void func1(); void func2(int param); }
-
完结
void MySpace::func1(){ cout << "MySpace::func1" << endl; } void MySpace::func2(int param){ cout << "MySpace::func2 : " << param << endl; }
-
声明
- 无名命名空间
意味着命名空间中的标识符只能在本文件内拜访
适当于给这个标识符加上了static,使得其能够作为内部衔接
namespace { int a = 10; void func(){ cout << "hello namespace" << endl; } } void test(){ cout<< "a : " << a << endl; func(); }
- 命名空间别号
namespace veryLongName{ int a = 10; void func(){ cout << "hello namespace" << endl; } } void test(){ namespace shortName = veryLongName; cout << "veryLongName::a : " << shortName::**a **<< endl; veryLongName::func(); shortName::func(); }
3.3 using声明
- 声明可使得指定的标识符可用
namespace A{ int paramA = 20; int paramB = 30; void funcA(){ cout << "hello funcA" << endl; } void funcB(){ cout << "hello funcA" << endl; } } void test(){ //1. 经过命名空间域运算符 cout << A::paramA<< endl; A::funcA(); //2. using声明 using A::paramA; using A::funcA(); cout << paramA << endl; //cout << paramB << endl; //不行直接拜访 funcA(); //3. 同名抵触 //int paramA = 20; //相同效果域留意同名抵触 }
- using声明碰到函数重载
namespace A{ void func(){} void func(int x){} int func(int x,int y){} } void test() { using A::func; func(); func(10); func(10,20); }
假设命名空间包含一组用相同姓名重载的函数,using声明就声明晰这个重载函数的一切集合。
3.4 using编译指令
- using编译指令使整个命名空间标识符可用
namespace A{ int paramA = 20; int paramB = 30; void funcA(){ cout << "hello funcA" << endl; } void funcB(){ cout << "hello funcB" << endl; } } void test01() { using namespace A; cout << paramA << endl; cout << paramB << endl; funcA(); funcB(); //不会发生二义性 int paramA = 30; cout << paramA << endl; } namespace B { int paramA = 20; int paramB = 30; void funcA(){ cout << "hello funcA" << endl; } void funcB(){ cout << "hello funcB" << endl; } } void test02() { using namespace A; using namespace B; //二义性发生,不知道调用A仍是B的paramA //cout << paramA << endl; }
留意:
- 运用using声明或using编译指令会添加命名抵触的或许性。
- 也便是说,假设有称号空间,并在代码中运用效果域解析运算符,则不会呈现二义性。
3.5 命名空间运用
- 咱们刚讲的一些东西一开端会觉得难一些,这些东西今后仍是挺常用,只需理解了它们的作业机理,运用它们十分简略。
- 需求记住的关键问题是当引进一个大局的using编译指令时,就为该文件翻开了该命名空间
- 它不会影响任何其他的文件,所以能够在每一个完结文件中调整对命名空间的操控。比方:
- 假设发现某一个完结文件中有太多的using指令而发生的命名抵触,就要对该文件做个简略的改动
- 经过明确的限定或许using声明来消除姓名抵触,这样不需求修正其他的完结文件
三、变量监测与类型加强
1. 大局变量检测增强
- C言语代码:
int a = 10; //赋值,当做界说 int a; //没有赋值,当做声明 int main(){ printf("a:%d\n",a); return EXIT_SUCCESS; }
- 此代码在C++下编译失败,在C下编译经过.
2. C++中一切的变量和函数都有必要有类型
- C言语代码
//i没有写类型,能够是恣意类型 int fun1(i){ printf("%d\n", i); return 0; } //i没有写类型,能够是恣意类型 int fun2(i){ printf("%s\n", i); return 0; } //没有写参数,代表能够传任何类型的实参 int fun3(){ printf("fun33333333333333333\n"); return 0; } //C言语,假设函数没有参数,主张写void,代表没有参数 int fun4(void){ printf("fun4444444444444\n"); return 0; } g(){ return 10; } int main(){ fun1(10); fun2("abc"); fun3(1, 2, "abc"); printf("g = %d\n", g()); return 0; }
- 以上C代码C编译器编译可经过,C++编译器无法编译经过。
- 在C言语中,
int fun()
表示回来值为int
,承受恣意参数的函数,int fun(void)
表示回来值为int的无参函数。 - 在C++ 中,
int fun()
和int fun(void)
具有相同的意义,都表示回来值为int
的无参函数
- 在C言语中,
- 以上C代码C编译器编译可经过,C++编译器无法编译经过。
3. 更严厉的类型转化
- 在C++,不同类型的变量一般是不能直接赋值的,需求相应的强转
- C言语代码:
typedef enum COLOR{ GREEN, RED, YELLOW } color; int main(){ color mycolor = GREEN; mycolor = 10; printf("mycolor:%d\n", mycolor); char* p = malloc(10); return EXIT_SUCCESS; }
- 以上C代码C编译器编译可经过,C++编译器无法编译经过
4. struct类型加强
- C中声明结构体
- 变量需求加上
struct
关键字 - C++不需求
- 变量需求加上
- C中的结构体
- 只能界说成员变量
- 不能界说成员函数。
- C++即
- 能够界说成员变量
- 也能够界说成员函数
- 示例代码:
//1. 结构体中即能够界说成员变量,也能够界说成员函数 struct Student{ string mName; int mAge; void setName(string name){ mName = name; } void setAge(int age){ mAge = age; } void showStudent(){ cout << "Name:" << mName << " Age:" << mAge << endl; } }; //2. c++中界说结构体变量不需求加struct关键字 void test01(){ Student student; student.setName("John"); student.setAge(20); student.showStudent(); }
四、bool类型、三目运算符
1. “新增”bool类型关键字
- 规范C++的
bool
类型有两种内建的常量true
(转化为整数1)和false
(转化为整数0)表示状态。
这三个姓名都是关键字- bool类型只要两个值,true(1值),false(0值)
- bool类型占1个字节巨细
- 给bool类型赋值时,非0值会主动转化为true(1),0值会主动转化false(0)
- 示例代码:
void test() { cout << sizeof(false) << endl; //为1,//bool类型占一个字节巨细 bool flag = true; // c言语中没有这品种型 flag =100; //给bool类型赋值时,非0值会主动转化为true(1),0值会主动转化false(0) }
-
C言语中的bool类型
- 在C99规范之前是没有bool关键字,C99规范现已有bool类型,包含头文件stdbool.h,就能够运用和C++相同的bool类型。
2. 三目运算符功用增强
- C言语三目运算表达式回来值为数据值,为右值,不能赋值。
int a = 10; int b = 20; printf("ret:%d\n", a > b ? a : b); //考虑一个问题,(a > b ? a : b) 三目运算表达式回来的是什么? //(a > b ? a : b) = 100; //回来的是右值
- C++言语三目运算表达式回来值为变量本身(引证),为左值,能够赋值。
int a = 10; int b = 20; printf("ret:%d\n", a > b ? a : b); //考虑一个问题,(a > b ? a : b) 三目运算表达式回来的是什么? cout <<"b:"<< b << endl; //回来的是左值,变量的引证 (a > b ? a : b) = 100;//回来的是左值,变量的引证 cout << "b:" << b << endl;
- 可被赋值的表达式:
C++的有些表达式是能够被赋值的
-
左值和右值概念
- 在C++中能够放在赋值操作符左面的是左值,能够放到赋值操作符右面的是右值。
- 有些变量即能够当左值,也能够当右值。
- 左值为Lvalue,L代表Location,表示内存能够寻址,能够赋值。
- 右值为Rvalue,R代表Read,便是能够知道它的值。
- 比方:
int temp = 10;
temp在内存中有地址,10没有,可是能够Read到它的值
五、C/C++中的const
1. const概述
-
const
是常量的意思,被其润饰的变量不行修正- 假设润饰的是
类
、结构体(的指针)
,其成员也不能够更改 - const润饰的是其右边的内容
- 以下5个指针分别是什么意义?
- 上面的指针问题能够用以下结论来处理:
- 假设润饰的是
- const是C/C++中的一个关键字,是一个限定符
- 它用来限定一个变量不答应改动
- 它将一个方针转化成一个常量。
const int a = 10 a = 100; //编译过错,const是一个常量,不行修正
2. C/C++中const的差异
2.1 C中的const
- 常量的引进是在C++前期版本中,其时规范C规范正在拟定。
- 那时,虽然C委员会决定在C中引进const,可是,他们C中的const理解为”一个不能改动的一般变量”
- 也便是以为const应该是一个只读变量
- 既然是变量那么就会给const分配内存
- 而且在C中const是一个大局只读变量,C言语中const润饰的只读变量是
外部衔接
的。
- 假设这么写:
const int arrSize = 10; int arr[arrSize];
- 看似是一件合理的编码,可是这将得出一个过错。因为arrSize占用某块内存,所以C编译器不知道它在编译时的值是多少?
2.2 C++中的const
在C++中,一个const不用创立内存空间,而在C中,一个const总是需求一块内存空间
- 在C++中,
是否为const常量分配内存空间依赖于怎样运用
- 一般说来,假设一个const仅仅用来把一个姓名用一个值替代(就像运用#define相同),那么该存储局空间就不用创立
- 假设存储空间没有分配内存的话,在进行完数据类型查看后,为了代码愈加有用,值也许会折叠到代码中。
- 不过,取一个const地址, 或许把它界说为extern,则会为该const创立内存空间
- 在C++中,呈现在一切函数之外的const效果于整个文件(也便是说它在该文件外不行见),默以为
内部衔接
,C++中其他的标识符一般默以为外部衔接
2.3 C/C++中const异同总结
2.3.1 大局const
- C言语大局const会被存储到只读数据段
- C++中大局const当声明extern或许对变量取地址时,编译器会分配存储地址,变量存储在只读数据段
- 两个都受到了只读数据段的保护,不行修正
- 以上代码在C/C++中编译经过,在运转期,修正constA的值时,发生写入过错。原因是修正只读数据段的数据
const int constA = 10; int main(){ int* p =(int*)&constA; *p = 200; }
2.3.2 部分const
- C言语中部分const存储在
仓库区
- 仅仅不能经过变量直接修正const只读变量的值
- 可是能够跳过编译器的查看,经过指针直接修正const值
const int constA = 10; int* p = (int*)&constA; *p = 300; printf("constA:%d\n",constA); printf("*p:%d\n", *p);
- 示例代码
const int constA = 10; int* p = (int*)&constA; *p = 300; printf("constA:%d\n",constA); printf("*p:%d\n", *p);
- 运转成果:
- 运转成果:
- C言语中,经过指针直接赋值修正了constA的值
-
C++中关于
部分的const
变量要差异对待:-
- 关于
根底数据类型
,也便是const int a = 10这种
编译器会把它放到符号表中,不分配内存
当对其取地址时,会分配内存
const int constA = 10; int* p = (int*)&constA; *p = 300; cout << "constA:"<< constA << endl; cout << "*p:" << *p << endl;
- constA在符号表中,当咱们对constA取地址,这个时候为constA分配了新的空间
-
*p
操作的是分配的空间,而constA是从符号表获得的值
- 关于
-
- 关于
根底数据类型
,假设用一个变量初始化const变量,假设const int a = b,那么也是会给a分配内存
int b = 10; const int constA = b; int* p = (int*)&constA; *p = 300; cout << "constA:"<< constA << endl; cout << "*p:" << *p << endl;
- 关于
-
- 关于自定数据类型,比方类方针,那么也会分配内存
const Person person; //未初始化age //person.age = 50; //不行修正 Person* pPerson =(Person*)&person; //指针直接修正 pPerson->age = 100; cout <<"pPerson->age:" << pPerson->age << endl; pPerson->age = 200; cout << "pPerson->age:" <<pPerson->age << endl;
- 运转成果:
- 为person分配了内存,所以咱们能够经过指针的直接赋值修正person方针。
-
- C 中const默以为外部衔接,C++中const默以为内部衔接.
- 当C言语两个文件中都有const int a的时候,编译器会报重界说的过错。
- 而在C++中,则不会,因为C++中的const默许是内部衔接的。假设想让C++中的const具有外部衔接,有必要显示声明为: extern const int a = 10;
- const 由C++采用,并加进规范C中,虽然他们很不相同。
- 在C中,编译器对待const好像对待变量相同,只不过带有一个特殊的标记,意思是”你不能改动我”。
- 在C++中界说const时,编译器为它创立空间,所以假设在两个不同文件界说多个同名的const,链接器将发生链接过错。简而言之,const在C++顶用的更好。
- 了解: 能否用变量界说数组:
- 在支撑C99规范的编译器中,能够运用变量界说数组。
-
- 微软官方描绘vs2013编译器不支撑C99:
- Microsoft C conformsto the standard for the C language as set forth in the 9899:1990 edition of theANSI C standard.
-
- 以下代码在Linux GCC支撑C99编译器编译经过
int a = 10; int arr[a]; int i = 0; for(;i<10;i++) arr[i] = i; i = 0; for(;i<10;i++) printf("%d\n",arr[i]);
2.4 尽量以const替换#define
- 宏界说,是预处理器的一个功用。宏界说的操作只进行内容替换,不进行语法查看
- 在旧版本C中,假设想树立一个常量,有必要运用预处理器”
#defineMAX 1024
咱们界说的宏MAX从未被编译器看到过,因为在预处理阶段,一切的MAX现已被替换为了1024,所以MAX并没有将其参加到符号表中
- 但咱们运用这个常量获得一个编译过错信息时,或许会带来一些困惑,因为这个信息或许会说到1024,可是并没有说到MAX.假设MAX被界说在一个不是你写的头文件中,你或许并不知道1024代表什么,也许处理这个问题要花费很长时刻。
- 处理办法便是用一个常量替换上面的宏:
const int max= 1024;
const和
#define
差异总结:
-
-
const
有类型,可进行编译器类型安全查看。#define
无类型,不行进行类型查看
-
-
-
const
有效果域,而#define
不重视效果域,默许界说处到文件完毕.假设界说在指定效果域下有用的常量,那么#define
就不能用。
-
- Case1:
- 宏常量没有类型,所以调用了int类型重载的函数
- const有类型,所以调用期望的short类型函数?
- 示例代码:
#define PARAM 128 const short param = 128; void func(short a){ cout<< "short!" << endl; } void func(int a){ cout<< "int" << endl; }
- Case2:
- 宏常量不重视效果域.
void func1(){ const int a = 10; #define A 20 //#undefA //卸载宏常量A } void func2(){ //cout <<"a:" << a << endl; //不行拜访,超出了const int a效果域 cout<< "A:" << A << endl; //#define效果域从界说到文件完毕或许到#undef,可拜访 } int main(){ func2(); return EXIT_SUCCESS; }
- 宏常量不重视效果域.
- 问题: 宏常量能够有命名空间吗?
namespace MySpace{ #define num 1024 } void test(){ //cout << MySpace::NUM<< endl; //过错 //int num = 100; //命名抵触 cout<< num << endl; }
六、引证(reference)
1. 引证根本用法
- 界说
- 在C言语中,运用指针(Pointer)能够直接获取、修正某个变量的值
- 在C++中,运用引证(Reference)能够起到跟指针类似的功用
-
引证是C++对C的重要扩大
- 在C/C++中指针的效果根本都是相同的
- 可是C++添加了别的一种给函数传递地址的途径,这便是按引证传递(pass-by-reference)
- 它也存在于其他一些编程言语中,并不是C++的创造。
- 变量名实质上是一段接连内存空间的别号,是一个标号(门牌号)
- 程序中经过变量来请求并命名内存空间
- 经过变量的姓名能够运用存储空间
- 在C/C++中指针的效果根本都是相同的
- 对一段接连的内存空间只能取一个别号吗?
- C++中新增了引证的概念,引证能够作为一个已界说变量的别号 (昵称,小名,笔名)
- 根本语法:
Type& ref = val;
- 引证存在的价值之一:
- 比指针更安全
- 函数回来值能够被赋值
- 常引证(Const Reference)
- 引证能够被const润饰,这样就无法经过引证修正数据了,能够称为常引证
- const有必要写在&符号的左面,才能算是常引证
- const引证的特色
- 能够指向临时数据(常量、表达式、函数回来值等)
- 能够指向不同类型的数据(根本数据类型、枚举、结构体、类、指针、数组等,都能够有引证)
- 作为函数参数时(此规矩也适用于const指针)
- 能够承受const和非const实参(非const引证,只能承受非const实参)
- 能够跟非const引证构成重载
- 当常引证指向了不同类型的数据时,会发生临时变量,即引证指向的并不是初始化时的那个变量
- 留意事项:
- &在此不是求地址运算,而是起标识效果
- 类型标识符是指方针变量的类型
- 引证适当所以变量的别号
- 对引证做核算,便是对引证所指向的变量做核算
- 在界说的时候就有必要初始化,引证初始化之后不能改动,即:
一旦指向了某个变量,就不能够再改动,“从一而终”
- 能够利用引证初始化另一个引证,适当于某个变量的多个别号
- 能够树立对数组的引证
- 不存在【引证的引证、指向引证的指针、引证数组】
- 不能有NULL引证,有必要保证引证是和一块合法的存储单元关联
- 知道引证
- 代码示例1
//1. 知道引证 void test01(){ int a = 10; //给变量a取一个别号b int& b = a; cout<< "a:" << a << endl; cout<< "b:" << b << endl; cout<< "------------" << endl; //操作b就适当于操作a本身 b= 100; cout<< "a:" << a << endl; cout<< "b:" << b << endl; cout<< "------------" << endl; //一个变量能够有n个别号 int& c = a; c= 200; cout<< "a:" << a << endl; cout<< "b:" << b << endl; cout<< "c:" << c << endl; cout<< "------------" << endl; //a,b,c的地址都是相同的 cout<< "a:" << &a << endl; cout<< "b:" << &b << endl; cout<< "c:" << &c << endl; } //2. 运用引证留意事项 void test02(){ //1) 引证有必要初始化 //int& ref; //报错:有必要初始化引证 //2) 引证一旦初始化,不能改动引证 int a = 10; int b = 20; int& ref = a; ref= b; //不能改动引证 //3) 不能对数组树立引证 int arr[10]; //int& ref3[10] = arr; }
- 代码示例2
//1.树立数组引证办法一 typedef int ArrRef[10]; int arr[10]; ArrRef& aRef = arr; for (int i = 0; i < 10;i ++){ aRef[i] = i+1; } for (int i = 0; i < 10;i++){ cout<< arr[i] << " "; } cout<< endl; //2. 树立数组引证办法二 int(&f)[10] = arr; for (int i = 0; i < 10; i++){ f[i] = i+10; } for (int i = 0; i < 10; i++){ cout << arr[i] << " "; } cout<< endl;
- 代码示例1
2. 函数中的引证
最常见看见引证的地方是在函数参数
和回来值
中
- 参数
- 当引证被用作函数参数的时,在函数内对任何引证的修正,将对还函数外的参数发生改动。
- 当然,能够经过传递一个指针来做相同的作业,但引证具有更明晰的语法。
- 回来值
- 假设从函数中回来一个引证,有必要像从函数中回来一个指针相同对待。
- 当函数回来值时,引证关联的内存一定要存在
代码示例:
// 值传递 void ValueSwap(int m,int n){ int temp = m; m= n; n= temp; } //地址传递 void PointerSwap(int* m,int* n){ int temp = *m; *m = *n; *n = temp; } //引证传递 void ReferenceSwap(int& m,int& n){ int temp = m; m= n; n= temp; } void test(){ int a = 10; int b = 20; //值传递 ValueSwap(a, b); cout<< "a:" << a << " b:" << b << endl; //地址传递 PointerSwap(&a, &b); cout<< "a:" << a << " b:" << b << endl; //引证传递 ReferenceSwap(a, b); cout<< "a:" << a << " b:" << b << endl; }
- 经过引证参数发生的效果同按地址传递是相同的。引证的语法更清楚简略:
-
- 函数调用时传递的实参不用加“&”符
-
- 在被调函数中不用在参数前加“*”符
-
- 引证作为其它变量的别号而存在,因而在一些场合能够替代指针。C++主张用引证传递取代地址传递的办法,因为引证语法容易且不易出错。
- 代码示例:
//回来部分变量引证 int& TestFun01(){ int a = 10; //部分变量 return a; } //回来静态变量引证 int& TestFunc02(){ static int a = 20; cout<< "static int a : " << a << endl; return a; } int main(){ //不能回来部分变量的引证 int& ret01 = TestFun01(); //假设函数做左值,那么有必要回来引证 TestFunc02(); TestFunc02() = 100; TestFunc02(); return EXIT_SUCCESS; }
- 不能回来部分变量的引证
- 函数当左值,有必要回来引证
- 代码示例:
3. 引证的实质
- 引证的实质在C++内部完结是一个指针常量.
Type& ref = val; // Type* const ref = &val;
- 仅仅编译器削弱了它的功用
引证便是弱化了的指针
- 一个引证占用一个指针的巨细
- C++编译器在编译进程中运用常指针作为引证的内部完结
- 因而引证所占用的空间巨细与指针相同,仅仅这个进程是编译器内部完结,用户不行见。
//发现是引证,转化为 int* const ref = &a; void testFunc(int& ref){ ref= 100; // ref是引证,转化为ref = 100 } int main(){ int a = 10; int& aRef = a; //主动转化为 int const aRef = &a;这也能阐明引证为什么有必要初始化 aRef= 20; //内部发现aRef是引证,主动帮咱们转化为: *aRef = 20; cout<< "a:" << a << endl; cout<< "aRef:" << aRef << endl; testFunc(a); return EXIT_SUCCESS; }
4. 指针引证
- 在C言语中假设想改动一个指针的指向而不是它所指向的内容,函数声明或许这样:
void fun(int**);
- 给指针变量取一个别号。
Type* pointer =NULL; Type*& = pointer;
- 关于C++中的界说那个,语法明晰多了。函数参数变成指针的引证,用不着获得指针的地址。
5. 常量引证
- 常量引证的界说格式:
- 常量引证留意:
- 字面量不能赋给引证,可是能够赋给const引证
- const润饰的引证,不能修正。
- const引证运用场景
- 常量引证首要用在函数的形参,尤其是类的复制/复制结构函数。
- 将函数的形参界说为常量引证的优点:
- 引证不发生新的变量,削减形参与实参传递时的开支。
- 因为引证或许导致实参随形参改动而改动,将其界说为常量引证能够消除这种副效果。
- 假设期望实参跟着形参的改动而改动,那么运用一般的引证,假设不期望实参跟着形参改动,那么运用常引证
- 代码示例:
//const int& param避免函数中意外修正数据 void ShowVal(**const int&** param){ cout<< "param:" << param << endl; }
6. 数组的引证
- 常见的2种写法
七、函数
1. 函数
因为本文通篇是针对C++对C的扩展的C++常识介绍,若您对函数的根本语法等现已淡忘,欢迎学习我这篇文章里面临函数的介绍:
- 温习C言语中心常识|函数、效果域规矩、数组、枚举、字符与字符串、指针
2. 函数重载(overload)
2.1 函数重载概述
- 能使姓名方便运用,是任何程序设计言语的一个重要特征!
- 咱们现实生活中常常会碰到一些字在不同的场景下具有不同的意思,比方汉语中的多音字“重”
- 当咱们说: “他好重啊,我都背不动!”咱们依据上下文意思,知道“重”在此时此地表示重量的意思
- 假设咱们说“你怎样写了那么多重复的代码? 维护性太差了!”这个地方咱们知道,“重”表示重复的意思
- 相同一个字在不同的场景下具有不同的意义。那么在C++中也有一品种似的现象呈现,同一个函数名在不同场景下能够具有不同的意义
-
函数名相同的函数
- 在传统C言语中,函数名有必要是仅有的,程序中不答应呈现同名的函数。在C++中是答应呈现同名的函数,这种现象称为函数重载。
- 函数重载的意图便是为了方便的运用函数名。
- 函数重载并不复杂,等大家学完就会明白什么时候需求用到他们,以及是怎样编译,链接的。
2.2 函数重载
2.2.1 函数重载根本语法
- 完结函数重载的条件:
- 同一个效果域
- 参数个数不同
- 参数类型不同
- 参数次序不同
- 事例:
//1. 函数重载条件 namespace A{ void MyFunc(){ cout << "无参数!" << endl; } void MyFunc(int a){ cout << "a: " << a << endl; } void MyFunc(string b){ cout << "b: " << b << endl; } void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;} void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;} } //2.回来值不作为函数重载依据 namespace B{ void MyFunc(string b, int a){} //int MyFunc(string b, int a){} //无法重载仅按回来值区分的函数 }
- 留意: 函数重载和默许参数一同运用,需求额外留意二义性问题的发生。
void MyFunc(string b){ cout<< "b: " << b << endl; } //函数重载碰上默许参数 void MyFunc(string b, int a = 10){ cout<< "a: " << a << " b:" << b << endl; } int main(){ MyFunc("hello"); //这时,两个函数都能匹配调用,发生二义性 return 0; }
- 考虑:为什么函数回来值不作为重载条件呢?
- 当编译器能从上下文中确认仅有的函数的时,如int ret = func(),这个当然是没有问题的。可是,咱们在编写程序进程中能够忽略他的回来值。
- 那么这个时候,一个函数为void func(int x);另一个为int func(int x); 当咱们直接调用func(10),这个时候编译器就不确认调用哪个函数。
- 所以在C++中制止运用回来值作为重载的条件。
2.2.2 函数重载完结原理
- 编译器为了完结函数重载,也是默以为咱们做了一些暗地的作业,编译器用不同的参数类型来润饰不同的函数名,比方
- void func();编译器或许会将函数名润饰成_func
- 当编译器碰到void func(int x),编译器或许将函数名润饰为_func_int
- 当编译器碰到void func(int x,char c),编译器或许会将函数名润饰为_func_int_char
- 我这里运用”或许”这个字眼是因为编译器怎样润饰重载的函数称号并没有一个统一的规范,所以不同的编译器或许会发生不同的内部名。
- 事例: 以上三个函数在linux下生成的编译之后的函数名为:
2.3 函数重载|总结
- 规矩
- 函数名相同
- 同一个效果域、参数个数不同、参数类型不同、参数次序不同
- 留意
- 回来值类型与函数重载无关
- 调用函数时,实参的隐式类型转化或许会发生二义性
- 实质
- 采用了
name mangling
或许叫name decoration技能 - C++编译器默许会对符号名(比方函数名)进行改编、润饰,有些地方翻译为“命名倾轧”
- 重载时会生成多个不同的函数名,不同编译器(MSVC、g++)有不同的生成规矩
- 经过IDA翻开【VS_Release_制止优化】能够看到
- 采用了
2.4 extern “C”浅析
- 以下在Linux下测验:
- C函数:
void MyFunc(){}
,被编译成函数:MyFunc
- C++函数:
voidMyFunc(){}
,被编译成函数:_Z6Myfuncv
- 经过这个测验,因为C++中需求支撑函数重载,所以C和C++中对同一个函数经过编译后生成的函数名是不相同的
- 这就导致了一个问题,假设在C++中调用一个运用C言语编写模块中的某个函数,那么C++是依据C++的称号润饰办法来查找并链接这个函数,那么就会发生链接过错,以上例,C++中调用MyFunc函数,在链接阶段会去找Z6Myfuncv,成果是没有找到的,因为这个MyFunc函数是C言语编写的,生成的符号是MyFunc
- 那么假设我想在C++调用C的函数怎样办?
- C函数:
- extern”C”
- extern”C”的首要效果便是为了完结C++代码能够调用其他c言语代码。
加上extern "C"后,这部分代码编译器按C言语的办法进行编译和链接,而不是按C++的办法
。 - MyModule.h
#ifndef MYMODULE_H #define MYMODULE_H #include<stdio.h> #if __cplusplus extern "C"{ #endif void func1(); int func2(int a,int b); #if __cplusplus } #endif #endif
- MyModule.c
#include"MyModule.h" void func1(){ printf("hello world!"); } int func2(int a, int b){ return a + b; }
- TestExternC.cpp
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; #if 0 #ifdef __cplusplus extern "C" { #if 0 void func1(); int func2(int a, int b); #else #include"MyModule.h" #endif } #endif #else extern "C" void func1(); extern "C" int func2(int a, int b); #endif int main(){ func1(); cout<< func2(10, 20) << endl; return EXIT_SUCCESS; }
- extern”C”的首要效果便是为了完结C++代码能够调用其他c言语代码。
2.5 extern “C”|总结
- 被
extern "C"
润饰的代码会按照C言语的办法去编译
- 假设函数一起有声明和完结,
要让函数声明被extern "C"润饰
,函数完结能够不润饰 - 因为C、C++编译规矩的不同,在
C、C++混合开发
时,或许会常常呈现以下操作 - C++在调用C言语API时,需求运用
extern "C"
润饰C言语的函数声明 - 有时也会在编写C言语代码中直接运用
extern “C”
,这样就能够直接被C++调用 - 经过运用宏
__cplusplus
来区分C、C++环境 - 编写完C言语库,就要加宏界说(条件编译)区分C、C++环境
2.6 #pragma once
- 咱们常常运用#ifndef、#define、#endif来避免头文件的内容被重复包含
- 编写完C言语库,就要加宏界说(条件编译)避免重复包含
- #pragma once能够避免整个文件的内容被重复包含
- 差异
- #ifndef、#define、#endif受C\C++规范的支撑,不受编译器的任何限制
- 有些编译器不支撑#pragma once(较老编译器不支撑,如GCC 3.4版本之前),兼容性不够好
- #ifndef、#define、#endif能够针对一个文件中的部分代码,而#pragma once只能针对整个文件
3. 函数的默许参数
- (汇编:机器码 E8 打头,便是调用函数的机器指令;函数调用汇编指令为call)
- C++在声明函数原型的时可为一个或许多个参数指定默许(缺省)的参数值,当函数调用的时候假设没有指定这个值,编译器会主动用默许值替代
void TestFunc01(int a = 10, int b = 20){ cout<< "a + b = " << a + b << endl; } //留意点: //1. 形参b设置默许参数值,那么后边方位的形参c也需求设置默许参数 void TestFunc02(**int a,int b **= 10,**int c **= 10){} //2. 假设函数声明和函数界说分隔,函数声明设置了默许参数,函数界说不能再设置默许参数 void TestFunc03(int a = 0,int b **= 0); void TestFunc03(int a, int b){} int main(){ //1.假设没有传参数,那么运用默许参数 TestFunc01(); //2. 假设传一个参数,那么第二个参数运用默许参数 TestFunc01(100); //3. 假设传入两个参数,那么两个参数都运用咱们传入的参数 TestFunc01(100, 200); return EXIT_SUCCESS; }
- C++函数设置默许参数,在调用时能够依据状况省略实参。规矩如下:
- 默许参数只能按照右到左的次序
- 假设函数一起有声明、完结,默许参数只能放在函数声明中
- 默许参数的值能够是常量、大局符号(大局变量、函数名)
- 假设函数的实参常常是同一个值, 能够考虑运用默许参数
- 函数重载、默许参数或许会发生抵触、二义性(主张优先选择运用默许参数)
- 留意点:
- 函数的默许参数从右向左,假设一个参数设置了默许参数,那么这个参数之后的参数都有必要设置默许参数。
- 假设函数声明和函数界说分隔写,函数声明和函数界说不能一起设置默许参数
4. 函数的占位参数
- C++在声明函数时,能够设置占位参数
- 占位参数只要参数类型声明,而没有参数名声明。一般状况下,在函数体内部无法运用占位参数
- 什么时候用,在后边咱们要讲的操作符重载的后置++要用到这个
5. 内联函数(inlinefunction)
5.1 内联函数的引出
- C++从C中承继的一个重要特征便是功率
- 假设C++的功率显着低于C的功率,那么就会有很大的一批程序员不去运用C++了。
- 宏
- 在C中咱们常常把一些短而且履行频繁的核算写成宏,而不是函数,这样做的理由是为了履行功率,宏能够避免函数调用的开支,这些都由预处理来完结
- 可是在C++呈现之后,运用预处理宏会呈现两个问题:
- 第一个在C中也会呈现,宏看起来像一个函数调用,可是会有隐藏一些难以发现的过错
- 第二个问题是C++特有的,预处理器不答应拜访类的成员,也便是说预处理器宏不能用作类的成员函数
- 内联函数
- 为了坚持预处理宏的功率又添加安全性,而且还能像一般成员函数那样能够在类里拜访自若,C++引进了内联函数(inline function).
- 内联函数为了承继宏函数的功率,没有函数调用时开支,然后又能够像一般函数那样,能够进行参数,回来值类型的安全查看,又能够作为成员函数。
5.2 预处理宏的缺点
- 预处理器宏存在问题的关键是咱们或许以为预处理器的行为和编译器的行为是相同的。
- 当然也是因为宏函数调用和函数调用在表面看起来是相同的,因为也容易被混淆。可是其间也会有一些奇妙的问题呈现:
- 问题一:
#define ADD(x,y) x+y inline int Add(int x,int y){ return x + y; } void test(){ int ret1 = ADD(10, 20) * 10*; //期望的成果是300 int ret2 = Add(10, 20) 10; //期望成果也是300 cout<< "ret1:" << ret1 << endl; //210 cout<< "ret2:" << ret2 << endl; //300 }
- 问题二:
#define COMPARE(x,y) ((x) < (y) ? (x): (y)) int Compare(int x,int y){ return x < y ? x : y; } void test02(){ int a = 1; int b = 3; //cout <<"COMPARE(++a, b):" << COMPARE(++a, b) << endl; // 3 cout<< "Compare(int x,inty):" << Compare(++a, b) << endl; //2 }
- 问题三:
- 预界说宏函数没有效果域概念,无法作为一个类的成员函数,也便是说预界说宏没有办法表示类的范围
5.3 内联函数
5.3.1 内联函数根本概念
- 用内联函数替代预界说宏
- 在C++中,预界说宏的概念是用内联函数来完结的,而内联函数本身也是一个真实的函数。
- 内联函数具有一般函数的一切行为
- 仅有不同之处在于内联函数会在适当的地方像预界说宏相同打开,所以不需求函数调用的开支
- 因而应该不运用宏,运用内联函数。
- 内联函数
- 在一般函数(非成员函数)函数前面加上inline关键字使之成为内联函数。可是有必要留意有必要函数体和声明结合在一同,不然编译器将它作为一般函数来对待
- 事例:
- 一般函数
- 以上写法没有任何效果,仅仅是声明函数,应该如下办法来做:
- 内联函数
- 留意:
- 编译器将会查看函数参数列表运用是否正确,并回来值(进行必要的转化)。这些事预处理器无法完结的(也便是宏做不到的)
- 内联函数确实占用空间,可是内联函数相关于一般函数的优势仅仅省去了函数调用时候的压栈,跳转,回来的开支。咱们能够理解为内联函数是以空间换时刻
5.3.2 类内部的内联函数
- 为了界说内联函数,一般有必要在函数界说前面放一个inline关键字
- 可是在类内部界说内联函数时并不是有必要的。任何在类内部界说的函数主动成为内联函数
- 结构函数Person,成员函数PrintPerson在类的内部界说,主动成为内联函数
5.3.3 内联函数和编译器
- 内联函数并不是何时何地都有用,为了理解内联函数何时有用,应该要知道编译器碰到内联函数会怎样处理?
- 编译器与函数
- 关于任何类型的函数,编译器会将函数类型(包含函数姓名,参数类型,回来值类型)放入到符号表中。
- 相同,当编译器看到内联函数,而且对内联函数体进行剖析没有发现过错时,也会将内联函数放入符号表。
- 编译器与内联函数
- 当调用一个内联函数的时候,编译器首先保证:
- 传入参数类型是正确匹配的
- 或许假设类型不正彻底匹配,可是能够将其转化为正确类型
- 而且
- 回来值在方针表达式里匹配正确类型,或许能够转化为方针类型
- 内联函数就会直接替换函数调用,这就消除了函数调用的开支
- 假设内联函数是成员函数,方针this指针也会被放入适宜方位
- 当调用一个内联函数的时候,编译器首先保证:
- 类型查看和类型转化、包含在适宜方位放入方针this指针这些都是预处理器不能完结的
- 可是C++内联编译会有一些限制,以下状况编译器或许考虑不会将函数进行内联编译:
- 不能存在任何方式的循环句子
- 不能存在过多的条件判别句子
- 函数体不能过于巨大
- 不能对函数进行取址操作
- 内联仅仅仅仅给编译器一个主张,编译器不一定会承受这种主张,假设你没有将函数声明为内联函数,那么编译器也或许将此函数做内联编译。一个好的编译器将会内联小的、简略的函数。
5.3.4 内联函数|总结
- inline润饰符
- 运用inline润饰函数的声明或许完结,能够使其变成内联函数
- 主张声明和完结都添加inline润饰
- 特色
- 编译器会将函数调用直接打开为函数体代码
- 能够削减函数调用的开支
- 会增大代码体积
- 内联函数与宏
- 内联函数和宏,都能够削减函数调用的开支
- 对比宏,内联函数多了语法检测和函数特性
- 考虑以下代码的差异
- #define sum(x) (x + x)
- inline int sum(int x) { return x + x; }
- int a = 10; sum(a++);
- 留意
- 尽量不要内联超越10行代码的函数
- 有些函数即使声明为inline,也不一定会被编译器内联,比方递归函数
5.5 VS窥探内联的实质
专题系列文章
1. 前常识
- 01-探求iOS底层原理|总述
- 02-探求iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探求iOS底层原理|LLDB
- 04-探求iOS底层原理|ARM64汇编
2. 根据OC言语探究iOS底层原理
- 05-探求iOS底层原理|OC的实质
- 06-探求iOS底层原理|OC方针的实质
- 07-探求iOS底层原理|几种OC方针【实例方针、类方针、元类】、方针的isa指针、superclass、方针的办法调用、Class的底层实质
- 08-探求iOS底层原理|Category底层结构、App启动时Class与Category装载进程、load 和 initialize 履行、关联方针
- 09-探求iOS底层原理|KVO
- 10-探求iOS底层原理|KVC
- 11-探求iOS底层原理|探究Block的实质|【Block的数据类型(实质)与内存布局、变量捕获、Block的品种、内存办理、Block的润饰符、循环引证】
- 12-探求iOS底层原理|Runtime1【isa详解、class的结构、办法缓存cache_t】
- 13-探求iOS底层原理|Runtime2【音讯处理(发送、转发)&&动态办法解析、super的实质】
- 14-探求iOS底层原理|Runtime3【Runtime的相关使用】
- 15-探求iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探求iOS底层原理|RunLoop的使用
- 17-探求iOS底层原理|多线程技能的底层原理【GCD源码剖析1:主行列、串行行列&&并行行列、大局并发行列】
- 18-探求iOS底层原理|多线程技能【GCD源码剖析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探求iOS底层原理|多线程技能【GCD源码剖析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探求iOS底层原理|多线程技能【GCD源码剖析3:线程调度组dispatch_group、事件源dispatch Source】
- 21-探求iOS底层原理|多线程技能【线程锁:自旋锁、互斥锁、递归锁】
- 22-探求iOS底层原理|多线程技能【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探求iOS底层原理|内存办理【Mach-O文件、Tagged Pointer、方针的内存办理、copy、引证计数、weak指针、autorelease
3. 根据Swift言语探究iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、特点
、办法
、swift多态原理
、String
、Array
、Dictionary
、引证计数
、MetaData
等Swift根本语法和相关的底层原理文章有如下几篇:
- 01-Swift5常用中心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-Swift5常用中心语法|根底语法【Playground、常量与变量、常见数据类型、字面量、元组、流程操控、函数、枚举、可选项、guard句子、区间】
- 03-Swift5常用中心语法|面向方针【闭包、结构体、类、枚举】
- 04-Swift5常用中心语法|面向方针【特点、inout、类型特点、单例形式、办法、下标、承继、初始化】
- 05-Swift5常用中心语法|高档语法【可选链、协议、过错处理、泛型、String与Array、高档运算符、扩展、拜访操控、内存办理、字面量、形式匹配】
- 06-Swift5常用中心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码剖析】
4. C++中心语法
- 01-C++中心语法|C++概述【C++简介、C++起源、可移植性和规范、为什么C++会成功、从一个简略的程序开端知道C++】
- 02-C++中心语法|C++对C的扩展【::效果域运算符、姓名操控、struct类型加强、C/C++中的const、引证(reference)、函数】
- 03-C++中心语法|面向方针1【 C++编程规范、类和方针、面向方针程序设计事例、方针的结构和析构、C++面向方针模型初探】
- 04-C++中心语法|面向方针2【友元、内部类与部分类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转化、 C++规范、过错&&反常、智能指针】
- 05-C++中心语法|面向方针3【 承继和派生、多态、静态成员、const成员、引证类型成员、VS的内存窗口】
其它底层原理专题
1. 底层原理相关专题
- 01-核算机原理|核算机图形烘托原理这篇文章
- 02-核算机原理|移动终端屏幕成像与卡顿
2. iOS相关专题
- 01-iOS底层原理|iOS的各个烘托结构以及iOS图层烘托原理
- 02-iOS底层原理|iOS动画烘托原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
- 04-iOS底层原理|因CPU、GPU资源耗费导致卡顿的原因和处理计划
3. webApp相关专题
- 01-Web和类RN大前端的烘托原理
4. 跨渠道开发计划相关专题
- 01-Flutter页面烘托原理
5. 阶段性总结:Native、WebApp、跨渠道开发三种计划功能比较
- 01-Native、WebApp、跨渠道开发三种计划功能比较
6. Android、HarmonyOS页面烘托专题
- 01-Android页面烘托原理
- 02-HarmonyOS页面烘托原理 (
待输出
)
7. 小程序页面烘托专题
- 01-小程序结构烘托原理