C++学习:1基础语法
Posted 想文艺一点的程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++学习:1基础语法相关的知识,希望对你有一定的参考价值。
C++ 发展史
- C++ 03 只是修改了一些BUG
- C++ 11 :major
- C++ 17 :major
目录
一、基础语法 A
1、cin、cout
- cin、cout 当中的 c 是 console 控制台的意思。
- endl:end line 结束一行,就是换行的意思。
- cout:向控制态输出东西,箭头要指向cout。
- cin:想要从键盘当中读取东西,箭头要指向变量。
注意:只要main函数 return,只要 main 结束,控制台就会结束,所以需要 getchar() 来进行阻塞。
2、函数重载(Overload)
规则:
- 函数名字相同
- 参数个数不同、或者参数类型不同、或者顺序不同。(与参数名字无关、与函数返回值无关)
注意:
- C语言不支持函数重载的,但是很多语言都支持,比如说java
- 函数返回值不同,不能构成重载。
- 调用函数时,实参的隐式类型转换可能会产生二义性。
本质:
- 采用了 name mangling 或者叫做 name decoration 技术
- C++ 编译器默认会对符号名(比如函数名)进行改编、修饰。
- 虽然表面上看起来都是一个函数名,但是编译器会将其修改,将参数信息融合进去。所以支持了函数重载
使用vs调试分析反汇编
- 可以看出他们调用的并不是一个函数,(因为call 的地址都不一样)
- 但是还是无法窥探它的函数名
使用 IDA 来分析反汇编:(来查看它的本质函数名)
- 第一步:安装好 IDA 软件之后,直接将生成的可执行文件 exe 拖进去
- 关闭的时候
-
Debug模式: 很多调试信息, 生成的可执行文件比较臃肿
-
Release模式:去除调试信息,生成的可执行文件比较精简、高效
- 但是在release 模式下,编译器会帮我们优化代码。 (这样我们就又看不到 func 的函数原名了)
解决办法:让 release 不进行优化,不进行函数本体替换。
禁用之后,我们可以发现,它不进行替换了
- 继续使用 IDA 工具来进行查看。
3、默认参数
- C++允许函数设置默认参数,在调用时可以根据情况省略实参。
- 默认参数的赋值顺序,只允许是从右到左。
- 如果函数同时有声明、实现,默认参数只能放在函数声明中
- 默认参数的值可以是常量、全局符号(全局变量、函数名)
应用举例:有一个参数经常固定不变。
问题:**函数重载 **和 默认参数一起使用,可能会产生冲突、二义性(建议优先选择使用默认参数)
默认参数的本质:
没有使用默认参数:
- 传递参数使用 push 指令, push 4, push 2
如果有默认参数:
- 仍然还是 push 两次,所以本质上来说,还是传递了两次参数。
4、extern “C”
- 被extern "C"修饰的代码,会按照C语言的方式去编译
- 一般用在 C、C++ 的混合开发
第一个作用:被extern "C"修饰的代码,会按照C语言的方式去编译
问题:如果函数同时有声明和实现出现,extern ”C“ 应该加到哪里呢?
答:我们应该将 extern ”C“ 放在函数的声明前面。
C 和 C++混合开发
- 在使用 C++ 进行开发的时候,可能需要调用别人开发的第三方库,但是这个库是使用C语言来开发的。
- 逻辑上这样调用没错,但是编译器报错了。
分析:为什么会无法解析外部命令呢?
- 我们在 C++ 里面调用了 sum 函数,那么编译器一定会认为这个 sum 函数是 C++ 编译的。
- 但是这个函数确实是使用 C 语言来写的,也是用 C 语言的方式来进行编译的。
解决:在 C++ 里面 告诉编译器 sum 函数是使用C语言编译,让他调用的时候也使用 C++ 的方式去调用。
可以将我们的 extern ”C“ ,放到 C 语言API 对应的头文件当中, 然后在 C++ 的文件当中添加这个头文件即可。
在C语言库的开发者角度:就应该考虑到,我们的库可能被 C 语言开发使用,也有可能被 C++ 开发来使用。
- C 语言开发,C语言是不认识 extern ”C“ 的,所以会报错。
- 所以,如果添加了 extern ”C“ ,C 语言不认识,会报错。(不能使用C语言开发)
- 如果没有添加了 extern ”C“ ,C ++会以C++的方式去解析,会报错。(不能使用C++开发)
- 解决办法:使用条件编译。
二、基础语法 B
5、内联函数 (inline function)
定义:使用inline修饰函数的声明或者实现,可以使其变成内联函数 。建议**函数声明 **和 函数实现都增加inline修饰
特点:
- 编译器会将函数调用直接展开为函数体代码
- 可以减少函数调用的开销 (调用函数会开辟新的栈空间,会有跳转指令,又开销)
- 会增大代码体积
注意:
- 尽量不要内联超过10行代码的函数
- 有些函数即使声明为inline,也不一定会被编译器内联,比如递归函数
- inline 关键字仅仅只是建议编译器进行内联,但是编译器做不做不一定。
为什么要发明内联函数?
-
因为每次调用函数,都会开辟栈空间,还有跳转指令。
-
如果这个函数只调用一次,那么这点效率可以忽略。
-
但是如果这个函数被调用上百次呢? 累计起来节省的时间就很重要了。
什么时候使用内联函数?
- 代码体积比较小的
- 频繁调用的函数。
窥探内敛函数的本质:
注意:
- 在 Debug 模式下面,编译器默认是不会内联的。
- 将其修改为 relese、并且禁止优化。
禁止优化之后,我们可以看出内联函数并没有函数调用。
内联函数与宏
宏定义的本质也是进行替换,所以内联函数和宏定义都可以提高程序的效率。
内联函数比宏定义的好处:内联函数多了语法检测 和 函数特性
- 内联函数起码函数本身会进行语法检验。
一种应用情况
表达式
-
表达式可以被赋值。
-
表达式也是有返回值的
6、 const
-
const是常量的意思,被其修饰的变量不可修改 。
-
如果修饰的是 类、结构体(的指针),其成员也不可以更改 。
-
const 修饰的变量,必须在赋值的时候就被赋值。
使用 const 来修饰指针的时候,有两个情况:
- 指针本身不能修改
- 指针指向的变量不能修改
- const 修饰的是其右边的内容
const 右边的两种情况:
- *p1 :代表 p1 已经被解引用,所以指 p1 指向的变量。
- p2 : 代表 p2 本身。
7、引用(Reference)
- 在C语言中,使用指针(Pointer)可以间接获取、修改某个变量的值。
- 在C++中,使用引用(Reference)可以起到跟指针类似的功能 。(功能并不是完全一样)
注意:
-
引用相当于是变量的别名
-
对引用做计算,就是对引用所指向的变量做计算
-
在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”
(这点和const修饰的变量类似,因为以后不能修改,所以在初始化的时候就应该给赋值)
-
功能和 int * const p = &a; 类似。(指针本身不可以改变,但是指向的变量可以改变)
- 可以利用引用初始化另一个引用,相当于某个变量的多个别名
- 引用类型,必须和赋值的变量的类型相同。
引用的价值:比指针更加安全
- 引用是很安全的,因为一开始就得指向一个变量,而且以后不能修改指向。
- 指针就不一定了,可以发生改变,万一指向一个不能修改的区域,那么就可能发生不可预料的错误。
引用的价值:函数返回值可以被赋值 (值传递,地址传递)
- 可以发现引用比指针使用起来更加简单
问题:引用不是 “从一而终” 吗? 下列情况怎么解释?
- 每次调用一次函数,函数会被重新分配空间,形参都会重新定义。
引用的本质
-
引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针。
-
引用被初始化之后,就 “从一而终”, 而指针可以一直改变。
-
并且一个 引用的大小 和 指针的大小 相同。
-
x86 架构是 32 位系统,一个指针为 4 个字节。x64 架构是 64 位系统,一个指针为 8 个字节
- 借助结构体来查看引用大小(可以证明它的大小和指针一样大)
直接通过汇编代码来查看引用的本质:
然后进行比较:可以看出引用的汇编指令和指针的汇编指令是一样的。
8、常引用
(1)各种数据结构的引用 (引用的补充)
(2)常引用的应用场景:输入型参数,只想让他使用这个值,不想让他让他更改值。
比如:我们传入一个 Person 参数,我们只想让 test 函数打印这个人的信息,而不想让他来修改这个人的信息。
(3)常引用的特性:其他引用没有
- 可以指向 常量、临时数据、表达式、函数返回值
- 可以指向不同类型的数据:(语法糖)
指向不同类型的数据的时候会产生临时变量:从而引用就会指向这个临时变量
- 作形参的时候,可以接收 非const的形参 和 const 的形参
- 可以构成函数重载
三、汇编语言
汇编语言的分类:(汇编语言和 CPU 的架构直接挂钩的)
- 8086汇编(16bit)
- x86汇编(32bit)
- x64汇编(64bit):根据编译器的不同有两种书写格式:Intel 和 AT&T
- ARM汇编(嵌入式、移动设备)
注意:
- 汇编语言不区分大小写
- 在 windows 的编译器一般是 Intel 格式, 在Mac 和 Linux 平台的编译器一般是 AT&T 格式。
1、学习汇编指令的 2 大知识点:
- 寄存器 (CPU 当中有三大部分:寄存器、运算器、控制器)
- 汇编指令
AX、BX、CX、DX 这四个通用寄存器的发展:
- 8086:16位cpu,叫做 AX、BX、CX、DX
- x86 : 32位cpu,叫做 EAX、EBX、ECX、EDX
- x64 : 64位菜谱,叫做 RAX、RBX、RCX、RDX
注意:x64的汇编是兼容 x86 的汇编。
- RAX 的 低4个字节是 EAX。
- EAX 的 低2个字节是 AX。
- AX 的低1个字节是 AL ,高一个字节是 AH。
寄存器既然是做信息存储的,那么一个寄存器的空间到底是多大呢?(和 CPU 架构有关)
- R开头的寄存器是64bit的,占8字节 。(64位系统)
- E开头的寄存器是32bit的,占4字节 。 (32位系统)
2、内联汇编
什么是内联汇编? 在 C++ 当中嵌入汇编代码。
- 可以看出 AL 就是 EAX 的低一个字节。
3、汇编指令
基础铺垫:
- [ 地址值 ] :中括号[ ] 里面放的都是内存地址
- word是2字节, dword是4字节(double word), qword是8字节(quad word)
- 很多指令都是以逗号进行分割,所以我们先找逗号。
mov 指令
用一个字节来存放3
char a= 3;
mov [a] , 3
mov [ffff0001h] , 3 # 将 3 放到 ffff0001h 内存处
用两个字节大小来存放 3,使用 ptr 来指定单位大小 (ptr 左侧就是存放大小)
int a= 3;
mov dword ptr [a] , 3
mov dword ptr [ffff0001h] , 3 # 将 3 放到 ffff0001h 内存处
从指定内存开始,取出 4 个字节内容,放到 eax 当中。(一般内存向高地址增长)
mov eax, dowrd ptr [1128h] #1128h、1129h、1130h、1131、
-
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈的栈顶。
-
EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈的底部。
-
可以看出定义变量的本质:将一个数值进行压栈。
call 指令
格式: call 函数地址 ()
可以看出 test 函数的地址为 0x0331000 .(可以看出最后一条指令是 ret,可以进行调用返回)
func 的地址是 0x0331010 (可以看出最后一条指令是 ret,可以进行调用返回)
lea指令(指针)
-
lea dest, [ 地址值 ]。 (lea:load effect address)
-
将地址值赋值给dest,类似于dest = 地址值
-
出现于指针、引用
注意:因为 mov 指令不可以直接交换两个内存的值,所以需要寄存器进行中转。
ret、add、sub、inc、dec、xor
ret :函数返回
xor op1, op2 :将op1和op2异或的结果赋值给op1,类似于 op1 = op1 ^ op2
add op1, op2 :类似于 op1 = op1 + op2
sub op1, op2 :类似于 op1 = op1 - op2
inc op :自增,类似于 op = op + 1
dec op :自减,类似于 op = op – 1
jmp
jmp 内存地址 :跳转到某个内存地址去执行代码
- j开头的一般都是跳转,分为无条件跳转,有条件跳转。
- 大多数是带条件的跳转,一般跟test、 cmp等指令配合使用。 (一般用于分支指令)
- 跳转指令是不自带 ret 的。
常见代码的汇编
int a = 10; ********************** mov dword ptr [ebp-4],0Ah (立即数——>内存, 不需要寄存器中转)
int b = 20; ********************** mov dword ptr [ebp-8],14h (立即数——>内存, 不需要寄存器中转)
a = a + b; ********************** mov eax,dword ptr [ebp-4] (内存 ——> 寄存器, 需要寄存器中转)
********************** mov add eax,dword ptr [ebp-8] (在寄存器当中运算)
********************** mov mov dword ptr [ebp-4],eax (寄存器 ——> 内存,需要寄存器进行中转)
注意:mov 指令是不支持内存与内存之间,直接进行值传递。只能先借用 寄存器来进行中转。
int a = 10;
int *p = &a;
int &ref = a;
注意:指针和引用的本质都一样,都是借助于 lea 指令。
以上是关于C++学习:1基础语法的主要内容,如果未能解决你的问题,请参考以下文章