C/C++教程

C

本文主要是介绍C,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、创建一个项目

File -> New -> Projects -> win32 Console Application -> Ok -> A "Hello, World!" application -> Finish -> Ok -> FileView -> test.cpp

1、快捷键

F5 运行
F7 编译
F9 断点
shitg + F5 结束程序

2、关闭程序

File -> Close Workspace

3、打开一个文件

File -> Open Workspace

4、寄存器窗口

在程序执行时,界面上面右键点击Registers

5、内存窗口

在程序执行时,界面上面右键点击Memory

6、进入反汇编

在程序执行时,右键Go To Disassembly

7、再反汇编窗口去掉C语言

再反汇编窗口右键点Source Annotation

8、再反汇编窗口显示硬编码

再反汇编窗口右键点 Code Bytes

9、

反汇编中进入某个程序按F11(相当于汇编中的F7)
F10(相当于汇编中的F8)

二、函数

1、无参数,无返回值的函数格式

# void 代表这个函数什么都不返回,返回什么类型void就改成什么类型
void 函数名()
{

}

2、有参数,有返回值

int Plus1(int x,int y)
{
	return x + y;
}

int main(int argc, char* argv[])
{
	Plus1(1,2);
	return 0;
}

3、函数调用函数

#include "stdafx.h"

int Plus1(int x,int y)
{
	return x + y;
}

int Plus2(int x,int y,int z)
{
	int t;
	int r;
	t = Plus1(x,y);
	r = Plus1(t,z);
	return r;
}

int main(int argc, char* argv[])
{
	Plus2(1,2,3);
	return 0;
}

4、裸函数

正常函数,编译器会自动帮我们生成一些东西,裸函数则是编译器啥都不生成

void __declspec(naked) Plus()
{
}


int main(int argc, char* argv[])
{
	Plus();
	return 0;
}

编译可以正常编译,但是执行会报错,原因是函数执行之后没有返回地址,需要手动添加

#include "stdafx.h"

void __declspec(naked) Plus()
{
        # 在编译器中写汇编代码如下
	__asm
	{
		ret
	}
}


int main(int argc, char* argv[])
{
	Plus();
	return 0;
}


5、裸函数实现1+2

正常函数编译器会自动帮我们生成,以下代码,裸函数需要我们手动添加以下代码

#include "stdafx.h"

int __declspec(naked) Plus(int x,int y)
{
  
	__asm
	{
                //保留调用函数前的栈底
		push ebp
                //提升堆栈
		mov ebp,esp
		sub esp,0x40
                //保存现场
		push ebx
		push esi
		push edi
                //填充缓冲区
		mov eax,0xcccccccc
		mov ecx,0x10
		Lea edi,dword ptr ds:[ebp-0x40]
		rep stosd
                //函数的核心功能,两个参数相加
		mov eax,dword ptr ds:[ebp+0x8]
		add eax,dword ptr ds:[ebp+0xc]
                //恢复现场
		pop edi
		pop esi
		pop ebx
                //降低堆栈
		mov esp,ebp
		pop ebp
                
		ret
	}
}


int main(int argc, char* argv[])
{
	Plus(1,2);
	return 0;
}

三、调用约定

调用约定 参数压栈顺序 平衡堆栈
__cdecl 从右至左入栈 调用者清理栈
__stdcall 从右至左入栈 自身清理堆栈
__fastcall ECX/EDX传送前两个,剩下:从右至左入栈 自身清理堆栈

1、__cdecl(编译器默认调用,外平栈)

#include "stdafx.h"

int __cdecl Plus1(int a, int b)					
{					
		return a+b;				
}	

int main(int argc, char* argv[])
{
	Plus1(1,2);
	return 0;
}

2、__stdcall(内平栈)

#include "stdafx.h"

int __stdcall Plus2(int a, int b)					
{					
	return a+b;				
}


int main(int argc, char* argv[])
{
	Plus2(1,2);
	return 0;
}

3、__fastcall

当传递的参数为2个时,用寄存器传递,速度较快,且不需要平栈
当参数多余两个时,与其它两种调用方式没啥区别了,在函数里面进行平栈

#include "stdafx.h"

int __fastcall Plu3(int a, int b)					
{					
	return a+b;				
}

int main(int argc, char* argv[])
{
	Plus3(1,2);
	return 0;
}

四、程序入口

1、程序入口

main是我们代码的入口的方法不是真正程序入口

int main(int argc, char* argv[])
{
	return 0;
}

真正的程序入口

五、数据类型

1、C语言数据类型

2、数据类型的三个要素

  • 存储数据的宽度
  • 存储数据的格式
  • 作用范围(作用域)

3、整数类型

char 8BIT 1字节

short 16BIT 2字节

int 32BIT 4字节

long 32BIT 4字节

4、无符号和有符号

#include "stdafx.h"

void Plus()
{
	char i = 0xFF;
	unsigned char k = 0xFF;
}


int main(int argc, char* argv[])
{
	Plus();
	return 0;
}

5、浮点类型

1、先将这个实数的绝对值化为二进制格式													

2、将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。													

3、从小数点右边第一位开始数出二十三位数字放入第22到第0位。 													

4、如果实数是正的,则在第31位放入“0”,否则放入“1”。													

5、如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。													

6、如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。													

6、ASCII

标准ASCII码只有7位,最高位永远是0(表示128可能的字符,表示常用的字符a-z,A-Z,0-9,逗号等)
扩展ASCII码,最高位永远是1(扩展ASCI码允许将每个字符的第8位用于确定附加的128 个特殊符号字符、外来语字母和图形符号)

#include "stdafx.h"

void Plus()
{
	char i = 'A';
}


int main(int argc, char* argv[])
{
	Plus();
	return 0;
}

7、GB2312

GB2312需要用两个字节来表示,一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,,后面一个字节(低字节)从0xA1,到0xFE,这样我们就可以组合出大约7000多个简体汉字了

8、内存图

9、变量

全局变量

  • 全局变量在程序编译完成后地址就已经确定下来了,只要程序启动,全局变量就已经存在了,启动后里面是否有值取决于声明时是否给定了初始值,如果没有,默认为0

  • 全局变量的值可以被所有函数所修改,里面存储的是最后一次修改的值.

  • 全局变量所占内存会一直存在,知道整个进程结束.

  • 全局变量的反汇编识别:
    MOV 寄存器,byte/word/dword ptr ds:[0x12345678]

局部变量

  • 局部变量在程序编译完成后并没有分配固定的地址.

  • 在所属的方法没有被调用时,局部变量并不会分配内存地址,只有当所属的程序被调用了,才会在堆栈中分配内存.

  • 当局部变量所属的方法执行完毕后,局部变量所占用的内存将变成垃圾数据.局部变量消失.

  • 局部变量只能在方法内部使用,函数A无法使用函数B的局部变量.

  • 局部变量的反汇编识别:
    [ebp-4]

10、分析参数

六、if语句

1、if语句

#include "stdafx.h"

int a;

void test(int x,int y)
{
	if(x>y)
	{
		a = x;	
	}
	
}

int main(int argc, char* argv[])
{
	test(3,5);
	return 0;
}


2、if else语句

// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

int g;

void test(int x,int y)
{
	if(x>y)
	{
		g = x + g;	
	}else
	{
		g = y + g;
	}
	
}






int main(int argc, char* argv[])
{
	test(3,5);
	return 0;
}

特点分析

  • 如果不跳转,那么会执行到jmp处,jmp直接跳转到END处
  • 如果跳转,则会直接跳过jmp END处的代码,直接执行后面的代码
  • 第一个jxx跳转的地址前面有一个jmp ,可以判断是if...else...语句

七、基础知识

1、声明和赋值

  • 声明变量就是告诉计算机,我要用一块内存,你给我留着,宽度和存储格式有数据类型决定

  • 计算机什么时候把这块内存给你,取决于变量的作用范围,如果是全局变量,在程序编译完就已经分配了空间,如果是局部变量,只有所在的程序被调用的时候,才会分配空间

  • 全局变量如果不赋初始值,默认是0,但局部变量在使用前一定要赋初值,不然就是CCCCCCCC

2、类型转换

MOVSX 先符号扩展,再传送(结果是FFFF,符号位是什么扩展的部分就是什么,MOVSX用于有符号)

MOV AL,0FF
MOVSX CX,AL
MOV AL,80
MOVSX CX,AL


MOVZX 先零扩展,再传送(结果是00FF,扩展的部分全填0,MOVZX用于无符号)
MOV AL,0FF
MOVZX CX,AL
MOV AL,80

3、表达式

  • 表达式无论多么复杂,都只有一个结果

  • 只有表达式,可以编译通过,但并不生成代码,需要与赋值或者其他流程控制语句一起组合的时候才有意义.

  • 当表达式中存在不同宽度的变量时,结果将转换为宽度最大的那个.

  • 当表达式中同时存在有符号和无符号数的时候,表达式的结构将转换为无符号数

4、语句

影响内存或者cpu的就叫语句

5、运算符

  • 关系运算符(“==”、“!=”、“>=”、“<=”、“>”、“<”)
  • 逻辑运算符(&& || ! )
  • 单目运算符(++i)
  • 三目运算符( x>y?x:y;)

八、循环语句

1、交换两个数

#include "stdafx.h"

void test()
{
	int x = 2;
	int y = 1;
	x = x + y;
	y = x - y;
	x = x - y;
	printf("%d:%d",x,y);
	
}

int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}

2、将一个数组倒序输出

#include "stdafx.h"



void test()
{
	int arr[5] = {2,4,1,5,3};
	int i = 4;
	while(i>=0)
	{
		printf("%d\n",arr[i]);
		i--;
	
	}
	
}

int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}

3、找出数组中最大的值

#include "stdafx.h"

void test()
{
	int arr[5] = {2,4,1,5,3};
	int r = arr[0];
	int i = 1;
	while(i<=4)
	{
		if(arr[i]>r)
		{
			r = arr[i];
		}
		i++;
	}
	printf("%d",r);	
}

int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}

4、写一个函数int prime(int x),如果x是素数返回值为1,否则返回0。

#include "stdafx.h"

int test(int x)
{
	int i = 2;

	while(i<x)
	{
		if(x%i == 0)
		{
			return 0;
		}
		i++;
	}
	return 1;

}

int main(int argc, char* argv[])
{
	int a = test(9);
	printf("%d",a);
	getchar();
	return 0;
}

5、俩俩比较数组的值,将最大的一个存储到数组的最后一个位置

#include "stdafx.h"

void test()
{
	int arr[5] = {2,5,1,4,3};
	int t = 0;
	int i = 0;
	while(i<4)
	{

		if(arr[i] > arr[i+1])
		{
			t = arr[i];
			arr[i] = arr[i+1];
			arr[i+1] = t;
		}
		i++;
	}

	int k = 0;
	while(k<5)
	{
		printf("%d\n",arr[k]);
		k++;
	
	}

}


int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}

6、判断数组是否是对称的,如果是返回1,不是返回0.

#include "stdafx.h"

int test()
{
	int arr[5] = {1,2,3,2,1};
	int t = 4;
	int i = 0;
	while(i<t)
	{

		if(arr[i] != arr[t])
		{
			return 0;
		}
		i++;
		t--;
	}
	return 1;

}

int main(int argc, char* argv[])
{
	int a =test();
	printf("%d",a);
	getchar();
	return 0;
}

7、冒泡排序

#include "stdafx.h"

void test()
{
	int arr[8] = {61,21,34,18,16,33,58,45};
	int lenght = 8;
	int i =0;
	int k = 0;
	while(i<lenght-1)
	{
		for(k=0;k<lenght-1-i;k++)
		{
			if(arr[k] > arr[k+1])
			{
				arr[k] = arr[k] + arr[k+1];
				arr[k+1] = arr[k] - arr[k+1];
				arr[k] = arr[k] - arr[k+1];
			}
		}
		i++;
	}


	int m = 0;
	while(m<8)
	{
		printf("%d\n",arr[m]);
		m++;
	}
}


int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}

九、参数传递

1、本机尺寸

  • 如果本机是32位的,那么对32位的数据支持最好,如果是64位的,那么对64位的支持最好

  • 整数类型的参数,一律使用int类型,char类型或者short类型的参数不但没有节省空间,反而浪费了多余的操作.

  • 参数传递的本质:将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数

2、局部变量

  • 小于32位的局部变量,空间在分配时,按32位分配.

  • 使用时按实际的宽度使用.

  • 不要定义char/short类型的局部变量.

  • 参数与局部变量没有本质区别,都是局部变量,都在栈中分配.

  • 完全可以把参数当初局部变量使用

#include "stdafx.h"

void test()
{

}


int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}


#include "stdafx.h"

void test()
{
	char a = 1;
	char b = 2;
}


int main(int argc, char* argv[])
{
	test();
	getchar();
	return 0;
}

3、64位宽度存取

#include "stdafx.h"
					
__int64 Function()				
{				
	__int64 x = 0x1234567890;			
	return x;			
}				

int main(int argc, char* argv[])
{
	__int64 x = Function();
	getchar();
	return 0;
}

4、比较char arr[3] 与 char arr[4] 哪个更省空间

char arr[4] :

void test()				
{				
	char arr[4] = {0};
}				
					
int main(int argc, char* argv[])
{
	test();

	return 0;
}

char arr[3]:

#include "stdafx.h"

					
					
void test()				
{				
	char arr[3] = {0};
}				
				
int main(int argc, char* argv[])
{
	test();

	return 0;
}

这篇关于C的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!