2、CPP 基础语法学习

很多语法点从汇编角度分析就很明了了

C++ 发展史

image-20210519094408496

  • C++ 03 只是修改了一些BUG
  • C++ 11 :major
  • C++ 17 :major

一、基础语法 A

1、cin、cout

image-20210519094634926

  • cin、cout 当中的 c 是 console 控制台的意思
  • endl:end line 结束一行,就是换行的意思。
  • cout:向控制态输出东西,箭头要指向cout
  • cin:想要从键盘当中读取东西,箭头要指向变量

注意:只要main函数 return,只要 main 结束,控制台就会结束,所以需要 getchar() 来进行阻塞


2、函数重载(Overload)

image-20210519100738910


规则:

  • 函数名字相同
  • 参数个数不同、或者参数类型不同、或者顺序不同。(与参数名字无关、与函数返回值无关)

注意:

  • C语言不支持函数重载的,但是很多语言都支持,比如说java
  • 函数返回值不同,不能构成重载
  • 调用函数时,实参的隐式类型转换可能会产生二义性。

image-20210519153028448


本质

  • 采用了 name mangling 或者叫做 name decoration 技术
  • C++ 编译器默认会对符号名(比如函数名)进行改编、修饰
  • 虽然表面上看起来都是一个函数名,但是编译器会将其修改,将参数信息融合进去。所以支持了函数重载

image-20210519152329301


使用vs调试分析反汇编

  • 可以看出他们调用的并不是一个函数,(因为call 的地址都不一样
  • 但是还是无法窥探它的函数名

image-20210519154611023


使用 IDA 来分析反汇编:(来查看它的本质函数名)

  • 第一步:安装好 IDA 软件之后,直接将生成的可执行文件 exe 拖进去

image-20210519161111144

image-20210519161043326

  • 关闭的时候

image-20210519162033418

  • Debug模式: 很多调试信息, 生成的可执行文件比较臃肿

  • Release模式:去除调试信息,生成的可执行文件比较精简、高效

image-20210519162422756

image-20210519161339661

image-20210519162221220

  • 但是在release 模式下,编译器会帮我们优化代码。 (这样我们就又看不到 func 的函数原名了

image-20210519163440768

解决办法:让 release 不进行优化,不进行函数本体替换。

image-20210519164032750

禁用之后,我们可以发现,它不进行替换了

image-20210519164212367

  • 继续使用 IDA 工具来进行查看。

image-20210519164602429

3、默认参数


  • C++允许函数设置默认参数,在调用时可以根据情况省略实参

image-20210519165640954

  • 默认参数的赋值顺序只允许是从右到左

image-20210519165923046

  • 如果函数同时有声明、实现,默认参数只能放在函数声明中

image-20210519170629818

  • 默认参数的值可以是常量全局符号全局变量、函数名

image-20210519171322874


应用举例:有一个参数经常固定不变

image-20210519172012686


问题:**函数重载 **和 默认参数一起使用,可能会产生冲突、二义性(建议优先选择使用默认参数)

image-20210519172340233


默认参数的本质

没有使用默认参数:

  • 传递参数使用 push 指令, push 4, push 2

image-20210519172607323

如果有默认参数:

  • 仍然还是 push 两次,所以本质上来说还是传递了两次参数

image-20210519173139489


4、extern “C”

  • 被extern "C"修饰的代码,会按照C语言的方式去编译
  • 一般用在 C、C++ 的混合开发

第一个作用:被extern "C"修饰的代码,会按照C语言的方式去编译

image-20210519180220087

image-20210519180505406

问题:如果函数同时有声明和实现出现,extern ”C“ 应该加到哪里呢?

答:我们应该将 extern ”C“ 放在函数的声明前面。

image-20210519180846228


C 和 C++混合开发
  • 在使用 C++ 进行开发的时候,可能需要调用别人开发的第三方库,但是这个库是使用C语言来开发的。
  • 逻辑上这样调用没错,但是编译器报错了

image-20210519182001602

分析:为什么会无法解析外部命令呢?

  • 我们在 C++ 里面调用了 sum 函数,那么编译器一定会认为这个 sum 函数是 C++ 编译的
  • 但是这个函数确实是使用 C 语言来写的,也是用 C 语言的方式来进行编译的。

解决:在 C++ 里面 告诉编译器 sum 函数是使用C语言编译,让他调用的时候也使用 C++ 的方式去调用

image-20210519182624544


可以将我们的 extern ”C“放到 C 语言API 对应的头文件当中, 然后在 C++ 的文件当中添加这个头文件即可。

image-20210519183326552

在C语言库的开发者角度:就应该考虑到,我们的库可能被 C 语言开发使用,也有可能被 C++ 开发来使用

  • C 语言开发,C语言是不认识 extern ”C“ 的,所以会报错。
  • 所以,如果添加了 extern ”C“ ,C 语言不认识,会报错。(不能使用C语言开发)
  • 如果没有添加了 extern ”C“ ,C ++会以C++的方式去解析,会报错。(不能使用C++开发)
  • 解决办法:使用条件编译

image-20210519184308117

image-20210519183931147


二、基础语法 B

5、内联函数 (inline function)

定义:使用inline修饰函数的声明或者实现,可以使其变成内联函数 。建议**函数声明 **和 函数实现都增加inline修饰

特点:

  • 编译器会将函数调用直接展开为函数体代码
  • 可以减少函数调用的开销 (调用函数会开辟新的栈空间,会有跳转指令,又开销)
  • 增大代码体积

注意:

  • 尽量不要内联超过10行代码的函数
  • 有些函数即使声明为inline,也不一定会被编译器内联,比如递归函数
  • inline 关键字仅仅只是建议编译器进行内联,但是编译器做不做不一定。

为什么要发明内联函数?

  • 因为每次调用函数,都会开辟栈空间,还有跳转指令。

  • 如果这个函数只调用一次,那么这点效率可以忽略。

  • 但是如果这个函数被调用上百次呢? 累计起来节省的时间就很重要了。

什么时候使用内联函数?

  • 代码体积比较小的
  • 频繁调用的函数。

窥探内敛函数的本质:

注意:

  • 在 Debug 模式下面,编译器默认是不会内联的。
  • 将其修改为 relese、并且禁止优化。

image-20210519214629918

禁止优化之后,我们可以看出内联函数并没有函数调用。

image-20210519215406029


内联函数与宏

宏定义的本质也是进行替换,所以内联函数和宏定义都可以提高程序的效率

内联函数比宏定义的好处:内联函数多了语法检测函数特性

  • 内联函数起码函数本身会进行语法检验

image-20210519221246822

一种应用情况

image-20210519222047971


表达式
  • 表达式可以被赋值

  • 表达式也是有返回值的

image-20210519222806471


6、 const

  • const是常量的意思,被其修饰的变量不可修改

  • 如果修饰的是 类、结构体(的指针),其成员也不可以更改

  • const 修饰的变量,必须在赋值的时候就被赋值

image-20210519223155731

使用 const 来修饰指针的时候,有两个情况:

  • 指针本身不能修改
  • 指针指向的变量不能修改
  • const 修饰的是其右边的内容

image-20210519224528575

const 右边的两种情况:

  • *p1 :代表 p1 已经被解引用,所以指 p1 指向的变量。
  • p2 : 代表 p2 本身。

image-20210519224957952


7、引用(Reference)

  • 在C语言中使用指针(Pointer)可以间接获取、修改某个变量的值。
  • 在C++中使用引用(Reference)可以起到跟指针类似的功能 。(功能并不是完全一样)

注意:

  • 引用相当于是变量的别名

  • 对引用做计算就是对引用所指向的变量做计算

  • 定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终

    (这点和const修饰的变量类似,因为以后不能修改,所以在初始化的时候就应该给赋值)

  • 功能和 int * const p = &a; 类似。(指针本身不可以改变,但是指向的变量可以改变)

image-20210519230720346

  • 可以利用引用初始化另一个引用,相当于某个变量的多个别名

image-20210519231612536

  • 引用类型,必须和赋值的变量的类型相同。

image-20210520002417868


引用的价值:比指针更加安全

  • 引用是很安全的,因为一开始就得指向一个变量,而且以后不能修改指向。
  • 指针就不一定了,可以发生改变,万一指向一个不能修改的区域,那么就可能发生不可预料的错误。

引用的价值:函数返回值可以被赋值 (值传递,地址传递)

  • 可以发现引用比指针使用起来更加简单

image-20210520001606777

问题:引用不是 “从一而终” 吗? 下列情况怎么解释?

  • 每次调用一次函数,函数会被重新分配空间,形参都会重新定义

image-20210520001953551


引用的本质
  • 引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针。

  • 引用被初始化之后,就 “从一而终”, 而指针可以一直改变。

  • 并且一个 引用的大小 和 指针的大小 相同

  • x86 架构是 32 位系统,一个指针为 4 个字节。x64 架构是 64 位系统,一个指针为 8 个字节

image-20210520003707733

  • 借助结构体来查看引用大小(可以证明它的大小和指针一样大

image-20210520004607659


直接通过汇编代码来查看引用的本质:

image-20210520101904235

image-20210520102004645

然后进行比较:可以看出引用的汇编指令和指针的汇编指令是一样的。

image-20210520102722028


三、汇编语言

汇编语言的分类:(汇编语言和 CPU 的架构直接挂钩的)

  • 8086汇编(16bit)
  • x86汇编(32bit)
  • x64汇编(64bit):根据编译器的不同有两种书写格式:IntelAT&T
  • ARM汇编(嵌入式、移动设备)

注意:

  • 汇编语言不区分大小写
  • 在 windows 的编译器一般是 Intel 格式, 在Mac 和 Linux 平台的编译器一般是 AT&T 格式。

image-20210520105909324

1、学习汇编指令的 2 大知识点:

  • 寄存器 (CPU 当中有三大部分:寄存器、运算器、控制器)
  • 汇编指令

image-20210520110237330


image-20210520112048485

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。

image-20210520111757350

image-20210520112204853

寄存器既然是做信息存储的,那么一个寄存器的空间到底是多大呢?(和 CPU 架构有关)

  • R开头的寄存器是64bit的,占8字节 。(64位系统)
  • E开头的寄存器是32bit的,占4字节 。 (32位系统)

2、内联汇编

什么是内联汇编? 在 C++ 当中嵌入汇编代码

  • 可以看出 AL 就是 EAX 的低一个字节。

image-20210520112637510


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),其内存放着一个指针,该指针永远指向系统栈最上面一个栈的底部

  • 可以看出定义变量的本质:将一个数值进行压栈

image-20210520151123145


call 指令

格式: call 函数地址 ()

image-20210520153211217

可以看出 test 函数的地址为 0x0331000 .(可以看出最后一条指令是 ret,可以进行调用返回)

image-20210520153244544

func 的地址是 0x0331010 (可以看出最后一条指令是 ret,可以进行调用返回)

image-20210520153353904


lea指令(指针)
  • lea dest, [ 地址值 ]。 (lea:load effect address)

  • 将地址值赋值给dest,类似于dest = 地址值

  • 出现于指针、引用

image-20210524090219791

image-20210524090607006

注意:因为 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 的。

image-20210524091532210


常见代码的汇编
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  		(寄存器 ——> 内存,需要寄存器进行中转)

image-20210524092404403

注意:mov 指令是不支持内存与内存之间,直接进行值传递。只能先借用 寄存器来进行中转。


int a = 10;
int *p = &a;
int &ref = a;

image-20210524090607006

注意:指针和引用的本质都一样,都是借助于 lea 指令


4、常引用

(1)各种数据结构的引用 (引用的补充)

image-20210524095328522

(2)常引用的应用场景:输入型参数,只想让他使用这个值,不想让他让他更改值。

比如:我们传入一个 Person 参数,我们只想让 test 函数打印这个人的信息,而不想让他来修改这个人的信息

image-20210524095925811

(3)常引用的特性:其他引用没有

  • 可以指向 常量、临时数据、表达式、函数返回值

image-20210524142431889

  • 可以指向不同类型的数据:(语法糖)

image-20210524142732820

指向不同类型的数据的时候会产生临时变量从而引用就会指向这个临时变量

image-20210524144837038

image-20210524144825241

image-20210524144811271

  • 作形参的时候,可以接收 非const的形参 和 const 的形参

image-20210524143517757

  • 可以构成函数重载

image-20210524144105603

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 如果你想学习 C++ 网络编程,你可以从以下几个方面入手: 1. 学习 C++ 基础知识。C++ 网络编程需要使用到 C++ 语言的基础知识,所以你需要先掌握 C++语法、数据类型、流程控制、函数等基础知识。 2. 了解网络基础知识。C++ 网络编程是在网络的基础上进行的,所以你需要了解一些网络基础知识,例如 IP 地址、端口号、TCP/IP 协议、HTTP 协议等。 3. 学习使用 C++ 的网络库。C++ 有很多网络库可供选择,例如 Winsock、Boost.Asio、Qt 网络模块等。你需要学习如何使用这些网络库进行网络编程。 4. 练习编写 C++ 网络程序。最后,你需要通过练习编写 C++ 网络程序来巩固所学知识,并不断提高自己的编程能力。 总的来说,学习 C++ 网络编程需要你有一定的 C++ 基础知识,并且要具备一定的网络基础知识。通过学习使用 C++ 的网络库,以及不断练习编写 C++ 网络程序,你就可以掌握 C++ 网络 ### 回答2: 学习C++网络编程可以按照以下几个步骤进行: 1. 充分掌握C++基础知识:在学习网络编程之前,需要对C++语言有一定的了解。可以通过学习相关的C++教程、书籍或在线教育平台来系统地学习C++语法、特性和面向对象编程等知识。 2. 学习计算机网络基础知识:网络编程离不开对计算机网络的基本概念和原理的理解。需要学习TCP/IP协议栈、网络通信模型(如客户端-服务器模型)以及常用网络编程概念(如IP地址、端口、socket等)等知识。 3. 学习网络编程库:网络编程可以借助各种网络编程库来简化开发过程。学习一些流行的网络编程库,如Boost.Asio、Poco、WinSock等,可以帮助更快地理解和应用网络编程的相关概念和技术。 4. 实践中锻炼:理论是学习的基础,但实践是巩固知识的关键。通过编写简单的网络应用程序,如客户端-服务器模型的聊天程序或文件传输程序等,来巩固和应用所学的网络编程知识。逐步增加项目的复杂性,提高对网络编程的实践能力和理解。 5. 学习网络安全:网络编程中,安全问题至关重要。学习关于网络安全的基本知识,如防火墙、加密算法、SSL/TLS等,可以帮助更好地保护网络应用程序的安全性。 此外,不断学习和探索是持续进步的关键。阅读相关领域的书籍、技术博客、参与开源项目等,可以帮助更深入地理解和应用网络编程技术。同时,关注行业的最新动态和技术发展,紧跟技术潮流,提高对网络编程的应用水平。 ### 回答3: 学习C++网络编程可以按照以下步骤进行: 1. 基础知识:首先,学习C++的基本语法和概念。了解C++的数据类型、运算符、控制流语句、函数等基础知识,为后续学习打下坚实的基础。 2. 网络基础:学习计算机网络的基本概念和协议,如TCP/IP协议、UDP协议等。了解网络编程中的一些基础知识,如IP地址、端口号、套接字等。 3. 学习编程库:掌握C++网络编程的常用编程库,如Boost.Asio、POCO C++ Libraries等。这些库提供了方便的接口和类,可以简化网络编程的复杂性。 4. 编程实践:通过实际的编程练习来加深对网络编程的理解和应用。可以尝试编写简单的网络应用程序,如客户端和服务器程序,实现基本的数据通信。 5. 阅读参考资料:阅读网络编程的相关书籍或在线教程,查阅文档和参考资料等。这些资料可以提供更深入的理论知识和实际操作的指导,帮助学习者更好地理解和应用网络编程。 6. 加入开发社区:参与网络编程相关的开发社区,与其他开发者交流、分享经验和问题。这样可以扩展自己的网络编程技能,获得更多的实践经验。 7. 持续学习:网络编程是一个广阔的领域,不断学习和深入研究可以提升自己的编程水平。同时,也要不断关注最新的网络技术和发展趋势,保持对网络编程领域的更新和了解。 总之,学习C++网络编程需要从基础知识开始,通过实践和不断学习来提高自己的编程技能。同时,也要保持积极的学习态度和持续的学习动力,才能更好地掌握和应用网络编程技术。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想文艺一点的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值