逆向基础之C语言 第一篇

逆向之C语言

  • 00_C语言概述
    • 知识点1 【写代码的过程】
      • Visual C++ 6.0
        • 安装VC6.0
        • 配置兼容性
        • 创建c工程
        • 添加源文件
    • 知识点2 【完整的C代码分析】
      • 1、案例1:hello iot
      • 2、案例2:求圆的面积
      • 3、案例3:用分函数的方式求两个数的和
  • 01_C语言的基本语法
    • 知识点1【数据类型】
      • 案例:验证数据类型的长度
    • 知识点2【unsigned和signed】(重要)
      • 1、无符号数unsigned
      • 2、有符号数signed 默认一般省略
    • 知识点3 【结构体struct 和 共用体union】
      • 1、struct 结构体中的 成员拥有 独立的空间
      • 2、union 共用体中的成员 共享同一份空间
    • 知识点4【enum枚举 和 void无类型】
    • 知识点5【其他关键字】(了解)
      • 1、register 寄存器变量
      • 2、typedef 为已有的类型取一个别名
      • 3、volatile 防止编译器优化 强制访问内存操作
    • 知识点6 【常量与变量】(重要)
      • 1、常量 值不能被修改 (千万不能反过来说:值不能被修改的就是常量X)
      • 2、变量 系统根据变量的类型 开辟对应的空间 其值可以被修改
    • 知识点7【八进制 十进制 十六进制】
    • 知识点9 【整型变量的操作 - 读(取值)、写(赋值)】
      • 1、取值与赋值——读写操作
      • 2、整型的输出形式
    • 知识点10 【实型 常量】
    • 知识点11 【字符常量和变量】(重要)
      • 1、字符常量char
        • 案例: 键盘输入“abc”,只取‘a’和‘c’
    • 知识点12 【转义字符】
    • 知识点13 【字符串】“”双引号作用
    • 知识点14 【输出的格式回顾】(了解)
    • 知识点15 【typedef】
  • 02_C语言的基本语句
    • 知识点1【数据类型转换】
      • 1、自动类型转换(保证精度不丢失)
        • **转化的方向**
        • 案例:有符号和无符号的转换
        • 案例:int和double的转换
        • 案例:char和short的类型转换
      • 2、强制类型转换
    • 知识点2 【运算符】
      • 1、算术运算符(+、-、*、/、%)
        • 案例:键盘输入一个数 ,是否能被3整除
      • 2、逻辑运算符(!、&&、||)
        • ① 逻辑非 ——!0 == 真 、 !真 == 假
        • ② 逻辑与 &&(重要)
        • ③逻辑或 ||
        • 综合案例:
    • 知识点3 【位运算符】二进制位操作(<< 、>> 、& 、| 、~ 、^)
      • &:按位与
      • |:按位或
        • 案例:将1010 1010的第2、3位变成1,其他不变
      • ~:按位取反
      • ^:按位异或 (重要)
        • 案例:将1010 1010的第0位发生翻转(要将哪位翻转,就将它与1异或)
      • <<:左移运算符(左边丢弃,右边补0)
      • >>:右移运算符
    • 知识点4【三目运算符】?:
      • 案例
    • 知识点5【逗号运算符】
      • 案例:逗号运算符
    • 知识点6【符合运算符】
      • 案例
    • 知识点7【自增自减 运算符】
    • 知识点7【优先级】分析已有的代码
    • 知识点8【if 语句】
      • 1、如果只在乎项目的一个结果,选择if
      • 2、如果项目只有**两种结果**,且**不会同时出现**。请选择if....else....
      • 3、如果一个项目 有多个结果 且不同时出现。选择if....else if...else if...else
      • 4、一个项目有多个结果,不确定是否同时出现
    • 知识点9【switch 选择语句】
      • 案例:有时候break可以省略
  • 03_循环语句及数组
    • 知识点1 【for循环】
      • 案例: 求1~100的和
      • 案例:
      • 案例:break只能跳出离它最近的一层循环
      • continue
      • 案例:求出0~100的所有偶数的和
      • break和continue的区别
      • 循环嵌套循环
    • 知识点2【while 循环 和 do...while 循环】
      • 案例:
      • 案例
    • 知识点3【goto 跳转】(慎用!)
    • 知识点4【数组的概述】
    • 知识点5【一维数值数组】
      • 1、数组的定义
        • 案例1:遍历数组
        • 案例2:数组的初始化
          • 全部初始化:
          • 部分初始化:
          • 拓展:特殊初始化
      • 2、数组的空间大小(重要)
        • 遍历数组
        • 案例:
      • 3、数组元素的操作
        • 案例:定义一个数组 5个元素,每个元素int类型,获取键盘输入
        • 作业:定义一个数组 5个元素,每个元素int类型,获取键盘输入,请求出数组的最大值,最小值,平均值。
    • 知识点6【二维数组】
      • 1、二维数组的定义
        • 二维数组的遍历:
        • 案例:
      • 2、二维数组的初始化
        • 分段初始化:用大括号里面的大括号,明确的表示一行
        • 连续初始化:放满一行才能放下一行
        • 案例:
  • 04_数组与函数
    • 知识点1 【作业讲解】
        • **作业:定义int arr[3][4]的二维数组,获取键盘输入,并求出每一行的平均值。**
        • 案例:求每一列的平均值
    • 知识点2 【字符数组】(重要)
      • 字符数组的初始化
      • 字符数组的遍历
        • 逐个字符初始化和字符串初始化的区别
      • 字符数组获取键盘的字符串
        • scanf和%s使用的时候有个缺点,遇到**空格**会结束输入
        • **gets()缺点:获取键盘输入的时候,不会管buf的大小,容易造成内存污染**
        • fgets():既可以获取带空格的字符串,也可以保证buf的不越界
          • 案例:
          • 提高案例:字符串的大小写的转换
    • 知识点3【二维字符数组】(了解)
      • 二维字符数组获取键盘输入的字符串:
    • 知识点4 【函数概述】
      • 1、函数的定义
      • 2、函数的声明
      • 3、函数的调用
    • 知识点5 【函数的参数】
      • 1、如果函数的形参什么都不写 在调用的时候可以传实参,只是实参得不到使用
      • 2、如果函数没有参数,请将形参写成void
      • 3、函数参数的传递

00_C语言概述


知识点1 【写代码的过程】

编辑器:程序员写代码的过程(记事本、VC6.0、vim…)(让程序员看懂)
编译器:查看代码的语法错误,生成汇编语言。
汇编器:将生成好的汇编语言生成二进制语言(也叫做目标文件)
连接器:将生成好的二进制语言+用到的库+启动代码=====>可执行文件

  • Windows开发特点:
    • 在电脑上装一个VC或者其他集成开发环境
    • 编辑程序——》编译程序——》看现象——》有问题——》修改程序——》调试程序 ——》看现象
  • Linux开发特点:
    • Linux下程序开发大多数通过在本地安装虚拟机、物理机或者网络连接到服务器完成
    • 出于效率、远程开发、嵌入式开发的考虑:开发方式太多 是在命令行下完成,没有很好的集成开发环境供我们使用

Visual C++ 6.0

安装VC6.0

Visual C++6.0下载地址:https://pan.baidu.com/s/1UoyJD6FUARS2EvDdC9l7qA 提取码: 1314.
解压单击安装即可,默认下一步

配置兼容性

  • 鼠标右键点击桌面生成的快捷方式,选择属性
  • 点击兼容性,找到兼容模式的位置,在“以兼容模式运行这个程序”前打勾,在下方下拉菜单中选择Windows XP(Service Pack3),点击确定即可
    -逆向基础之C语言 第一篇_第1张图片

创建c工程

打开VC6.0,关闭每日提示
文件——》新建——》工程——》win32 console Application控制台程序——》确定
逆向基础之C语言 第一篇_第2张图片
从0开始学,选择一个空工程——》完成
逆向基础之C语言 第一篇_第3张图片
单击FileView就可以看到工程的目录结构
逆向基础之C语言 第一篇_第4张图片

添加源文件

文件——》新建——》文件——》C++ Source File——》写文件名(后缀名为.C,若不写则默认为.cpp为C++源文件)——》确定
逆向基础之C语言 第一篇_第5张图片
逆向基础之C语言 第一篇_第6张图片

知识点2 【完整的C代码分析】

1、案例1:hello iot

# include   //std是标准的意思   i表示输入  o表示输出(标准的输入输出头文件)
// 双斜杠//是行注释  /**/是块注释,注意块注释不能嵌套

//()里面的是函数的形参(函数外部将数据传递到函数内部的桥梁)
int main(int argc, char *argv[])
//main主函数 是程序的入口,有且仅有一个主函数
//main左边的int代表的是函数的返回类型
{
     
	// 使用任何东西  必先存在
	printf("hello iot\n");// printf来自系统库文件,printf:将双引号的字符串输出(o)到终端上
	return 0;//0是一个int类型
}
//{}函数的功能都在大括号里面实现,每敲一行代码记得用tab缩进,不要用空格
//分号是C语言的结束标记
//如果你的代码一闪而过  可以用带阻塞的代码  getchar();
// int  char return都是关键字

总结:
1、main有且只有一个
2、printf的头文件是stdio.h
3、注释 行注释和块注释(块注释不能嵌套)

调整VC6.0字体大小:工具——》选项——》三角符号往后点有个格式——》选择原窗口——》选择合适大小
逆向基础之C语言 第一篇_第7张图片

2、案例2:求圆的面积

知道的条件:半径r
算法:面积=πrr
解析步骤:
①r通过键盘输入或者用特定的值
②定义一个面积变量 area=πrr
③将圆的面积输出到终端上

# include 	
# define PI 3.14 //定义一个宏  建议用大写表示(和普通变量区分开)

int main(int argc,char *argv[])
{
     
	float r=0.0f;//定义一个r变量  系统给r开辟4字节的空间
	float area=0.0f;//定义一个面积变量

	//获得半径  从键盘获得scanf(要使用必先存在)
	printf("请输入圆的半径r:");
	scanf("%f",&r); //对r变量取地址   scanf是带阻塞的
	//测试
	//printf("r=%f\n",r);//测试有没有得到r

	//算法:计算面积 area=PI * r * r  要使用面积就要先存在
	//float area=0.0f; //在VC6.0中不能这样定义(必须在语句的前方),在visual studio就没有这个问题
	area=PI*r*r;

	//将面积输出   %.2f中的.2表示小数部分保留两位
	printf("area=%.2f\n",area);

	return 0;
}
//不同的代码之间一定要空个行,这样代码更整洁

逆向基础之C语言 第一篇_第8张图片
运行结果
逆向基础之C语言 第一篇_第9张图片

3、案例3:用分函数的方式求两个数的和

步骤分析:
1、定义两个变量data1、data2 获取键盘输入

2、定义一个函数 去计算上面两个变量data1、data2的和
在函数内部计算计算(怎么将数据传递到函数内部?)
需要用形参 将data1 data2传递到函数内部

3、得到函数的计算结果(怎么得到呢?)
通过函数的返回值 得到函数的计算结果

#include

int add_fun(int a,int b) //ret是整型  data1和data2也是整型
{
     
	return a+b;
}
int main(int argc, char *argv[])
{
     
	//定义两个变量  获取键盘输入
	//int data1=0;
	//int data2=0;
	int data1=0,data2=0;
	int ret=0; //定义一个ret去存放函数结果

	printf("请输入两个int变量:");
	scanf("%d %d",&data1,&data2);//门牌号?

	//函数的调用
	ret=add_fun(data1,data2);// a=data1  b=data2

	//输出ret的结果
	printf("ret=%d\n",ret);

	return 0;
}

运行结果:
逆向基础之C语言 第一篇_第10张图片

01_C语言的基本语法


知识点1【数据类型】

数据分为不同类型 是为了 更加合理的利用内存空间
计算机存储的是二进制,一位二进制只能存放0或者1(低电平或高电平)
1byte=8bit (0000 0000~ 1111 1111)
1kb=1024byte 10月24日是程序员节
1MB=1024kb
1GB=1024MB
1TB=1024GB
1PB=1024TB
1EB=1024PB

-----------------------------------在32位平台-----------------------------------------
char 字符类型 占1个字节 (8个二进制位)
short 短整型 占2个字节 (16个二进制位)
int 整型 占4个字节(32个二进制位)
long 长整型 占4字节空间(32个二进制位)
float 单精度浮点型 占4字节空间(32个二进制位)
double 双精度浮点型 占8字节空间(64个二进制位)

-----------------------------------在64位平台-----------------------------------------
char 字符类型 占1个字节 (8个二进制位)
short 短整型 占2个字节 (16个二进制位)
int 整型 占4个字节(32个二进制位)
long 长整型 占8字节空间(64个二进制位)
float 单精度浮点型 占4字节空间(32个二进制位)
double 双精度浮点型 占8字节空间(64个二进制位)

案例:验证数据类型的长度

sizeof是测量类型的长度

#include
int main(int argc,char *argv[])
{
     
	printf("sizeof(char)=%d\n",sizeof(char));
	printf("sizeof(short)=%d\n",sizeof(short));
	printf("sizeof(int)=%d\n",sizeof(int));
	printf("sizeof(long)=%d\n",sizeof(long));
	printf("sizeof(float)=%d\n",sizeof(float));
	printf("sizeof(double)=%d\n",sizeof(double));
	return 0;
}

运行结果:
逆向基础之C语言 第一篇_第11张图片

知识点2【unsigned和signed】(重要)

1、无符号数unsigned

数据没有符号位,自身所有二进制位都是数据位
比如:unsigned char ——》 0000 0000~ 1111 1111

2、有符号数signed 默认一般省略

二进制的最高位为符号位,其他为是数据位
signed char ——》 xxxx xxxx(x为0或1)
最高位为1表示负数,最高位为0表示正数
比如:一个字节: -10==1000 1010

案例:

#include
int main(int argc,char *argv)
{
     
	//定义一个有符号int
	signed  int num1=10; //系统给num1开辟4字节的空间(这个4字节的最高位是符号位)

	// signed 默认是省略的(推荐)
	int num2=10; //num2也是有符号数

	//unsigned表示无符号数 不能省略
	unsigned int num3=10;

	return 0;
}

知识点3 【结构体struct 和 共用体union】

1、struct 结构体中的 成员拥有 独立的空间

struct data1{
     
	char a;
	short b;
	int c;
};
a b c就是结构体data1中的成员

逆向基础之C语言 第一篇_第12张图片

2、union 共用体中的成员 共享同一份空间

union data2{
     
char a;
short b;
int c;
};

逆向基础之C语言 第一篇_第13张图片

知识点4【enum枚举 和 void无类型】

1、enum枚举 将变量要赋值的值一一列举出来
enum BOOL(false,true);
enum BOOL bool=false;

2、void 无类型 (重要)
不能用void定义变量
int num; // √
void num; //X 系统不能确定给num开辟多大的空间


知识点5【其他关键字】(了解)

auto 自动类型 ,register 寄存器变量,static 静态变量,const 只读变量
sizeof 测类型的大小
typedef 为已有的类型 重新取一个别名
volatile 防止编译器优化

1、register 寄存器变量

逆向基础之C语言 第一篇_第14张图片
总结:
1、如果没有显示表明register,就类似int num,如果num被高频繁使用系统也会放入寄存器中。
2、register int num; //显示的将num放入寄存器中
3、寄存器的变量不能取地址 &num(X)

#include
int main(int argc,char *argv[]){
     
	register int num=10;
	// %p输出地址
	printf("%p\n",&num); //**错误**,不能对寄存器变量 取地址
	//我们讲的是内存地址,寄存器已经不在内存中,怎么取地址?
	return 0;
}

2、typedef 为已有的类型取一个别名

#include
void test01()
{
     
register int num=10;
	// %p输出地址
	//printf("%p\n",&num); //**错误**,不能对寄存器变量 取地址
}

typedef int INT32; //给已有的int类型 取了一个别名叫INT32
void test02()
{
     
	int num1=10;
	INT32 num=20;
	printf("num1=%d\n",num1);
	printf("num2=%d\n",num2);
}
	
int main(int argc,char *argv[]){
     
	test02();//想运行那个就写哪个
	return 0;
}

3、volatile 防止编译器优化 强制访问内存操作

逆向基础之C语言 第一篇_第15张图片

知识点6 【常量与变量】(重要)

1、常量 值不能被修改 (千万不能反过来说:值不能被修改的就是常量X)

10 20 4.14 ‘a’ “abv”

2、变量 系统根据变量的类型 开辟对应的空间 其值可以被修改

变量名num代表的是空间的内容
操作变量名就是对空间 内容 的操作

变量名的命名规则:由数字字母下划线组成,但是不能以数字开头

建议:小写英文字母 + "_"线 构成(C语言命名风格)
C++一般用小驼峰法命名:numName

知识点7【八进制 十进制 十六进制】

十进制: 0~9
八进制:0~7
十六进制:0 ~ 9 ,a ~ f

八进制 十进制 十六进制都是整型的输出形式
十进制输出 %d %u %ld %lu
八进制输出 %o
十六进制输出 %x

八进制是以0开头

printf("八进制:num=%#o\n",num); //0144

十六进制是以0x开头

printf("十六进制:num=%#x\n",num); //0x64

不同的进制仅仅是数据的表现形式, 并不能修改数据本身


知识点9 【整型变量的操作 - 读(取值)、写(赋值)】

1、取值与赋值——读写操作

void test01()
{
     
	//在大括号内部定义的变量叫局部变量(局部变量不初始化,内容随机)
	int data=0;
	int num=0;
	printf("num=%d\n",num);// 读操作   取值

	//写操作   赋值  
	num=100;
	printf("num=%d\n",num);

	data=num; //将右边的值赋值给左边 ,对于num来说是读取它的值100(读操作),赋值给data(写操作)
	printf("data=%d\n",data);

	//获取键盘输入
	printf("请输入一个int数据:");
	scanf("%d",&data); //&data  代表的是data空间的起始地址
	printf("data=%d\n",data);
}

运行结果:
逆向基础之C语言 第一篇_第16张图片

2、整型的输出形式

void test02()
{
     
	int num1=0;
	//%d 有符号int数据输出
	printf("num1=%d\n",num1);

	unsigned int num2=0;
	//%u 无符号int数据输出 (u是unsigned的简写)
	printf("num2=%u\n",num2)

	long num3=0;
	//%ld 有符号long型数据输出
	printf("num3=%ld\n",num3);

	unsigned long num4=0;
	//%lu 无符号long型数据输出
	printf("num3=%lu\n",num4);

	long long num5=0;
	//%ld 有符号long long型数据输出
	printf("num3=%lld\n",num3);

	unsigned long long num6=0;
	//%lu 无符号long long型数据输出
	printf("num3=%llu\n",num4);
	//注意:VC6.0不支持long long但在其他集成环境支持

	short num7=0; //short本来就是整型,所以一般不写int
	// %hd是有符号short数据输出
	printf("num7=%hd\n",num7);

	short num8=0; //short本来就是整型,所以一般不写int
	// %hu是无符号short数据输出
	printf("num8=%hu\n",num8);
}

unsigned short num6=0;
scanf(“num6=%hu\n”,num6); //输入
x

知识点10 【实型 常量】

注意:赋值语句 等号 两边类型尽量保证一致
%lf double型浮点型

加f的是float类型,不加f的是double类型

void test03()
{
     
	//不以f结尾的实型常量 为double类型
	printf("sizeof(3.14)=%d\n",sizeof(3.14));//8byte
	//sizeof针对于变量可以不加小括号,针对类型要加小括号
	//为了通用 最好都加小括号

	//以f结尾的实型常量 为float类型
	printf("sizeof(3.14)=%d\n",sizeof(3.14f));//4byte
	

	float f=3.14; //有没有问题?  赋值语句要保证两边类型相同
	// f是float类型4字节      3.14是double类型 8字节
	//把8字节赋值给4字节(这样赋值是有问题的)	float f=3.14f;(√)

	//%f输出 float数据
	printf("f=%f\n",f);
	
	//%lf 输出 double数据
	double d=3.14;
	printf("%d=%lf\n",d)

	scanf("%f",&f);
	scanf("%lf",&d);

}


float类型占四个字节
double类型占八个字节


知识点11 【字符常量和变量】(重要)

直接常量:用单引号括起来,如:‘a’ 、 ‘b’ 、‘12’等
转义字符:以反斜杠\开头,后面跟一个或者几个字符,如’\n’ '\t’等,分别代表换行、横向跳格

1、字符常量char

void test01()
{
     
	// %c输出的是字符  
	printf("%c\n",'a');//字符常量

	//字符变量
	//ch存储的是'a'的ASCII值
	char ch='a';
	// %c 输出的是符号本身
	printf("ch=%c\n",ch);// ch=a

	// %d 输出的是字符的ASCII值
	printf("ch=%d\n",ch);// ch=97

	// 'a'  单引号表示取字符的ASCII
	// 'a'==97是完全等价
	ch=97;
	printf("ch=%c\n",ch); //ch=a
	printf("ch=%d\n",ch); //ch=97


	ch=ch+1;
	printf("ch=%c\n",ch); //ch=b
	printf("ch=%d\n",ch); //ch=98

}

ASCII码表:


总结:
1、‘a’ 单引号表示取a的ASCII值
2、字符在计算机中存储的是ASCII
3、‘a’==97

思考:

char ch='a';
printf("%d\n",sizeof('a'));//4byte  sizeof测量的是小括号里面类型的长度
printf("%d\n",sizeof(char));//1byte  为什么?

‘a’,单引号是取字符的ASCII值——》97,本质是sizeof(97); 97是int类型,所以是4byte。

字符类型如何去获取键盘输入呢?

void test03()
{
     
	char ch;
	printf("请输入一个字符");
	//字符变量获取键盘输入
	scanf("%c",&ch);//scanf中%c只能**提取一个字符**
	printf("ch=%c\n",ch);//输出字符用%c
	printf("ch=%d\n",ch);//输出ASCII用%d

	//ch=getchar();//这也是获取一个字符,把值赋给变量ch
	//getchar是一个函数调用,其实scanf也是一个函数调用
}

运行结果:
逆向基础之C语言 第一篇_第17张图片
逆向基础之C语言 第一篇_第18张图片

案例: 键盘输入“abc”,只取‘a’和‘c’

逆向基础之C语言 第一篇_第19张图片

	char ch1,ch2;
	printf("请输入abc:");
	//提取a
	ch1=getchar();
	getchar();//丢弃一个字符
	ch2=getchar();//提取c

	printf("ch1=%c\n",ch1);
	printf("ch2=%c\n",ch2);

运行结果:逆向基础之C语言 第一篇_第20张图片

知识点12 【转义字符】

	// '\n'换行  '\t' tab
	//printf("##%c##\n",'\\');//两个反斜杠输出一个反斜杠
	//printf("#%%90#\n");//两个百分号输出一个百分号

	//ABCD中哪两个相等?BC
	//判断哪两个相等,其实就是查看这两个在计算机中存放的ASCII值是不是一样
	printf("A:%d\n",'0');//字符0的ASCII是48  字符本质存放的就是ASCII
	printf("B:%d\n",'\0');//’\0‘的ASCII值是0
	printf("C:%d\n",0); //0
	printf("D:%d\n","0");//字符串0  取的是字符0的地址

逆向基础之C语言 第一篇_第21张图片

知识点13 【字符串】“”双引号作用

逆向基础之C语言 第一篇_第22张图片
%s的本质作用就是:从字符串的首元素地址开始,逐个字符输出,遇到’\0’自动结束输出(重要)
在这里插入图片描述

void test06()
{
     
	//%s就是输出字符串
	//''取的是字符的ASCII值,双引号取的是字符串的首元素的地址
	//%s的本质作用就是:从字符串的首元素地址开始,逐个字符输出,遇到'\0'自动结束输出(重要)
	printf("%s\n","hello world");
	//系统会在字符串末尾自动添加一个结束字符'\0'
	printf("%d\n",sizeof("hello world"));//12byte  因为字符串默认以反斜杠0结尾

	printf("%s\n","hello\0world");//只能输出hello
	printf("##%s##\n","\0hello\0world");//####
}

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

请描述‘a’和“a”的区别:

1、单引号a只占一个字节,双引号a占2个字节(一个字节存放字符a,一个字节存放\0)
2、'a’取的是字符a的ASCII值,双引号a取的是字符串a的首元素的地址

printf("%d\n",sizeof("ab"));//3byte ——》‘a’‘b’‘\0’

"ab"作为类型代表的的字符串占空间的大小
“ab”作为地址 代表的是字符串 首元素 的地址


知识点14 【输出的格式回顾】(了解)

格式化输出字符:
%d 十进制有符号整数
%x 以十六进制表示的整数
%f float型浮点数
%e 指数形式的浮点数
%c 单个字符
%u 十进制无符号整数
%o 一八进制表示的整数
%lf double型浮点数
%s 字符串
%p 指针的值
特殊应用:
%3d , %03d , %-3d , %5.2f

void test07()
{
     
	printf("##########\n");
	//%5d表示占五个终端位宽  右对齐
	printf("##%5d##\n",123);
	//%-5d表示占五个终端位宽  左对齐
	printf("##%-5d##\n",123);
	//%05d表示占五个终端位宽  右对齐 不足补0
	printf("##%05d##\n",123);

	//千万不能写 %-05d(不起作用)
	printf("##%-05d##\n",123);

	//%5.2f  5表示总位宽为5   .2表示小数位保留两位
	printf("%5.2f\n",3.14159f);
}

运行结果:
逆向基础之C语言 第一篇_第23张图片

知识点15 【typedef】

已有的类型重新取个别名

步骤:

1、用已有的类型定义一个变量
2、用别名替换变量名
3、在整个表达式的前方加上typedef

案例1
int INT32
typedef int INT32;

案例2:给int arr[5]取个别名
int arr[5] //用已有的类型定义一个变量
typedef int ARR[5];//用别名替换变量名
ARR arr;//arr就是一个拥有5个int元素的数组

02_C语言的基本语句


知识点1【数据类型转换】

1、自动类型转换(保证精度不丢失)

转化的方向

逆向基础之C语言 第一篇_第24张图片

案例:有符号和无符号的转换

	int data1=-20; 
	unsigned int data2=10;
	//有符号数data1和无符号数data2进行转换的时候
	//会先将data1转换为无符号(-20的补码——》很大的正数)
	//很大的数 + 10 必然 >0
	if(data1+data2>0)
	{
     
		printf(">0\n");// √
	}
	else if(data1+data2<0)
	{
     
		printf("<0\n");
	}

运行结果是:大于0

案例:int和double的转换

	int data1=10;
	//int和double转换时,会将int转换为double
	printf("%d\n",sizeof(data1+3.14));// double是8字节

案例:char和short的类型转换

	char ch='a';
	short data=20;
	//由于char,short自身字节数过小,很容易溢出
	//所以,只要char,short参与运算,都会将自身转换成int
	printf("%d\n",sizeof(ch+ch));//4
	printf("%d\n",sizeof(data+ch));//4
	printf("%d\n",sizeof(data+data));//4

2、强制类型转换

(类型说明符)(表达式)
例如:
(float)a;
(int)(x+y);

void test11()
{
     
	float x=3.14f;
	int j=0;	
	//j=x;//赋值时,等号左右两边类型要匹配, j只能拿x的整数部分
	//强制类型转换,只是临时的转换,当前语句有效 在后面的语句中不会更改x的值
	j=(int)x;
	printf("j=%d,x=%f\n",j,x); //3
}

逆向基础之C语言 第一篇_第25张图片
注意:无论是强制转换还是自动转换,都只是为了本次运算的需要,而对变量的数据长度进行临时性转换,而不改变数据定义的类型。


知识点2 【运算符】

运算对象:常量、变量、函数(返回值)等

1、算术运算符(+、-、*、/、%)

a/b (a,b都为整数) 取整
%取余

	printf("%d\n",5/2);//2  取整 (5和2都为整数)
	printf("%d\n",5%2);//1  取余

案例:键盘输入一个数 ,是否能被3整除

	int num=0;
	//键盘输入一个整数
	printf("请输入一个整型数据:");
	scanf("%d\n",&num);
	//判断能否被3整除
	if(num%3==0)
	{
     
		printf("ok\n");
	}
	else{
     
		printf("no\n");

	}

逆向基础之C语言 第一篇_第26张图片

	printf("%d\n",5/2);//如果反斜杠两边都是整数,就是单纯的取整  2
	printf("%f\n",5/2.0f);//如果有浮点型,就是真正的除法  2.5

运行结果:
在这里插入图片描述

2、逻辑运算符(!、&&、||)

① 逻辑非 ——!0 == 真 、 !真 == 假

	printf("%d\n",!1);//0
	printf("%d\n",!0);//1

	//C中0为假,其他都为真  -1也是真
	printf("%d\n",!-1);//0

② 逻辑与 &&(重要)

A&&B —— A、 B同时为真 则 整个表达式,AB只要有一个为假,结果就为假。

	if((3>2) && (5>4))
	{
     
		printf("ok\n");//ok
	}
	else
	{
     
		printf("no\n");
	}

逻辑与有短路特性
A&&B ——》如果发现A为假,系统不会判断执行B,这就是短路特性
逆向基础之C语言 第一篇_第27张图片

③逻辑或 ||

A||B 只要AB任意一个为真,整个表达式都为,AB同时为假的时候,结果才为

逻辑非也有短路特性:只要A为真,编译器不会判断B的真假

	int num=10;
	printf("比较之前num=%d\n",num);//10
	(3>2) || (num=100);
	printf("比较之后num=%d\n",num);//10  num=100得不到执行

综合案例:

逆向基础之C语言 第一篇_第28张图片


知识点3 【位运算符】二进制位操作(<< 、>> 、& 、| 、~ 、^)


&:按位与

语法:全1为1,其他为0

    1010 1010
&   1111 0000
-------------------
	1010 0000 

特点:和1相与 保持不变,和0相与 清零

应用场景:将固定位清零


|:按位或

语法:有1就为1,全0才为0

    1010 1010
|   1111 0000
-------------------
	1111 1010

特点:和0相或 保持不变,和1相或置1
应用场景:将固定位置1

案例:将1010 1010的第2、3位变成1,其他不变

    1010 1010
|   0000 1100
-------------------
	1111 1010

~:按位取反

语法:0变1,1变0

 ~ 1010 1010=0101 0101

应用场景:配合&|操作


^:按位异或 (重要)

语法:相同为0,不同为1

    1010 1010
^   0000 1111
-------------------
	1010 0101

特点:和0异或保持不变,和1异或 取反
应用场景:将固定的位 发生高低电频翻转

案例:将1010 1010的第0位发生翻转(要将哪位翻转,就将它与1异或)

    1010 1010
^   0000 0001
-------------------
	1010 1011

<<:左移运算符(左边丢弃,右边补0)

(将二进制位向左移动)

注意:移动的位数不要超过自身长度

1010 1100 << 2(左移2位)
逆向基础之C语言 第一篇_第29张图片

>>:右移运算符

逆向基础之C语言 第一篇_第30张图片
右移分为:逻辑右移和算术右移

逻辑右移:右边丢弃,右边补0

算术右移
①无符号数:右边丢弃,左边补0
②有符号数:
<1>正数:右边丢弃,左边补0
<2>负数:右边丢弃,左边补1

右移基本上是右边丢弃,左边补0
只有负数且算术右移,才会左边补1

逻辑右移和算术右移是编译器决定,但是我们可以检测。

作业:自己写代码,判断你的编译器是逻辑右移还是算术右移

	char ch=0x85; // 1000 0101>>4
	//逻辑右移:1000 0101>>4 == 0000 1000 ==0x08
	//算术右移:1000 0101>>4 == 1111 1000 ==0xf8
	ch=ch>>4;
	printf("ch=%#x\n",ch);//0xf8是算术右移
	//逻辑右移、算术右移是编译器决定的,我们只能测试,不能改变

综合案例:将data第1,5位清零(按位与),3、4位置1,其他位保持不变
unsigned char data=0xaa; //1010 1010
//将data第1,5位清零
data = data & 1101 1101;// 与 完之后要赋值为data,才能更新它的值
1101 1101= ~(0010 0010)取反= ~(0010 0000 | 0000 0010

0010 0000= 0000 0001 <<5
0000 0010= 0000 0001 <<1

所以:1101 1101= ~(0x01 <<5 | 0x01<<1)

data=data & ~(0x01<<5 | 0x01 <<1);//很清楚的能看出是第一位和第五位清零

第3、4位 置1
data=data | 0001 1000;
0001 1000 = 0001 0000 | 0000 1000=0x01<<4 | 0x01<<3
data = data | (0x01<<4 | 0x01<<3);

规律:只有按位与有取反,清零就 与、取反,后面第几位就写左移第几位;要置1,只写或,不写取反,里面第几位,就写左移第几位,每一位用或或上


知识点4【三目运算符】?:

表达式1 ? 值1:值2
语法:如果表达式1为真,那么整个表达式的值为“值1”,反之为“值2”

案例

	int ret=0;//最好先初始化再去赋值
	ret=3>2 ? 5:6;
	printf("ret=%d\n",ret);//ret=5

知识点5【逗号运算符】

案例:逗号运算符

	int data1=0;
	int data2=0;
    //赋值运算符和逗号运算符,逗号运算符优先级其实是最低的
	data1=3,4,5,6;//先执行data1=3
	data2=(3,4,5,6);//小括号优先级最高,逗号运算符结合性是从左往右,所以data2=6
	printf("data1=%d\n",data1);//3
	printf("data2=%d\n",data2);//6

逆向基础之C语言 第一篇_第31张图片

知识点6【符合运算符】

+= 、 -= 、 = 、 /= 、%= 等等

a+=b ===》a=a+b
a * =b ===》a= a * b
注意:等号的右边必须看成一个整体

案例

	int data = 3;
	//将等号右边看成一个整体
	data *= 3+5;//data=data * (3+5);
	printf("data=%d\n",data);//24

知识点7【自增自减 运算符】

++i 或者 --i :先加、减 ,后使用
i++或者i–:先使用,后加、减

	int i=3;
	int j=0;
	j=++i;//先加,后使用
	//先执行i=i+1,i变为4;后执行j=i,4赋值给j,所以j也变成4
	printf("i=%d,j=%d\n",i,j);//4,4

逆向基础之C语言 第一篇_第32张图片
注意:i++或者++i作为单独的指令,是没有区别的


知识点7【优先级】分析已有的代码

优先级高的先执行,同级别的优先级要看结合性
C语言运算符的优先级
自己写代码,尽量加小括号
逆向基础之C语言 第一篇_第33张图片
逆向基础之C语言 第一篇_第34张图片

知识点8【if 语句】

1、如果只在乎项目的一个结果,选择if

if(表达式)
{
    语句1;
}
表达式为真,才会执行语句1;

void test21()
{
     
	int data=0;
	printf("请输入一个int数据:");
	scanf("%d",&data);
	//判断能被2整除
	if(data%2==0)//注意:这里不能写分号!
	{
     
		printf("能被2整除");
	}
}

2、如果项目只有两种结果,且不会同时出现。请选择if…else…

if(表达式)
{
    语句1;
}
else
{
    语句2;
}
表达式为真执行语句1,否则执行语句2

void test22()
{
     
	int data=0;
	printf("请输入一个int数据:");
	scanf("%d",&data);
	//判断data对2的余数(只有0,1两种情况)
	if(data % 2==0)
	{
     
		printf("余数为0\n");
	}
	else
	{
     
		printf("余数为1\n");
	}
}

3、如果一个项目 有多个结果 且不同时出现。选择if…else if…else if…else

if(表达式1)
{
    语句1;
}
else if(表达式2) //if后面要加表达式
{
    语句2;
}
else //可以省略
{
    语句n;
}
只有表达式1位真,执行语句1,;只有表达式2位真,执行语句2;所有表达式都为假,才执行语句n。

void test22()
{
     
	int data=0;
	printf("请输入一个int数据:");
	scanf("%d",&data);
	//判断data对3的余数(有0,1,2三种情况)
	if(data % 3==0)
	{
     
		printf("余数为0\n");
	}
	else if(data % 3==1)
	{
     
		printf("余数为1\n");
	}
	else if(data % 3==2)//其实这条语句可以直接写else,但是为了增加代码可读性,还是写else if吧
	{
     
		printf("余数为2\n");
	}
}

注意:只有前面的条件不满足,才会判断后面的条件,如果前面的条件满足,后面的条件不管真假都不会执行。

4、一个项目有多个结果,不确定是否同时出现

if(表达式1)
{
    语句1;
}
if(表达式2)
{
    语句2;
}
if(表达式3)
{
    语句3;
}
每个if语句都是独立的。

作业:键盘输入一个数,能否被3和7同时整除

知识点9【switch 选择语句】

switch(表达式)
{
case 值1: // 不能是实型、字符串,只能是整型和字符(本质是ASCII)
        语句1;
        break;

case 值2:
        语句2;
        break;

case 值3:
        语句3;
        break;

default: //可以省略
        语句n;
        break;
}

switch就是判断表达式的值是否和值1,值2,值3相等,来找到接下来程序的进入点。

	int data=0;
	printf("请输入一个int数据:");
	scanf("%d",&data);
	//判断data对3的余数(有0,1,2三种情况)
	switch(data%3){
     
	case 0:
		printf("余数为0\n");
		break;
	case 1:
		printf("余数为1\n");
		break;
	case 2:
		printf("余数为2\n");
		break;
	}

案例:有时候break可以省略

void test23()
{
     
	char ch=0;
	printf("请输入你的方向:wasd\n");
		ch=getchar();
	switch(ch)
	{
     
	case 'w':
	case 'W':
		printf("向上移动\n");
		break;

	case 'a':
	case 'A':
		printf("向左移动\n");
		break;

	case 's':
	case 'S':
		printf("向下移动\n");
		break;

	case 'd':
	case 'D':
		printf("向右移动\n");
		break;
	}

}

03_循环语句及数组


知识点1 【for循环】

for(初始语句; 循环条件 ; 步进条件)
{
     
	//循环语句
}
//初始语句:只在循环开始时执行一次
//循环条件:每次循环都要执行,如果循环条件为真,进入循环体  如果为假,退出循环体
//步进条件:每次循环结束的时候要执行的语句


逆向基础之C语言 第一篇_第35张图片

案例: 求1~100的和

	//1~100的和  1+2+3+4+...+100
	
	int i =0;
	int sum=0;
	
	for(i=1;i<=100;i++)// i++ ==> i=i+1
	{
     
		sum=sum+1
	}
	printf("sum=%d\n",sum);

案例:

	//1~100的和  1+2+3+4+...+100
	int i =1;
	int sum=0;

	//如果变量提前初始化了,for循环的初始化语句可以省略
	for(;;)//建议不要这么做
	{
     
		//如果for省略循环条件,必须在循环语句中实现退出循环的条件
		if(i>100)
			break;//跳出循环
		sum=sum+i;
		i++;//如果for省略步进条件,必须在循环语句中实现步进动作

	}
	printf("sum=%d\n",sum);

案例:break只能跳出离它最近的一层循环

void test26(){
     
	for(;;)
	{
     
		for(;;)
		{
     
			break;//只能跳出离他最近的一层循环
		}
	}

}

continue

continue:结束本次循环,立即从当前位置直接进入下一次循环
逆向基础之C语言 第一篇_第36张图片

作业:
1、求出1~100中能被7整除的数。

案例:求出0~100的所有偶数的和

	//求0  2  4  6...100的和
	int i=0;
	int sum=0;
	for(;i<=100;i=i+2)//步进条件不是单纯的i++
	{
     
		if(i==50)
			continue;//结束本次循环,立即从当前位置直接进入下一次循环
		sum+=i;
	}
	printf("sum=%d\n",sum);//2550

break和continue的区别

break语句用于跳出本层循环
continue用于结束本次循环

循环嵌套循环

//外层循环的次数 * 内层循环的次数 == 总循环次数
void test28(){
     
	int i=0;
	int j=0;
	for(i=0;i<10;i++)// 0~9=10
	{
     
		for(j=0;j<10;j++)// 0~9=10
		{
     
			printf("i=%d,j=%d\n",i,j);//输出几次?100次
		}
	}
}

知识点2【while 循环 和 do…while 循环】

while(循环条件)
{
     
	//循环语句
}

//如果循环条件为真就进入循环体执行循环语句

注意:
1、while没有初始化语句 ,用户要提前初始化
2、while没有步进语句, 用户必须在循环语句中写好步进语句。

案例:

	int i=1;
	int sum=0;//注意初始化
	while(i<=100)//1~100和
	{
     
		sum+=i;
		i++;
	}
	printf("sum=%d\n",sum);//5050

逆向基础之C语言 第一篇_第37张图片
注意:局部变量如果不初始化,内容不确定


do
{
     
	//循环语句
}while(循环条件);

先执行循环语句,在判断循环条件是否满足, 如果为真,进行下一次循环,如果为假,直接退出循环

案例

	int num=0;
	do
	{
     
		printf("ok\n");
	}while(num>0);//先进do再去判断条件,所以会将ok打印出来

知识点3【goto 跳转】(慎用!)

void test31(){
     
	printf("---------001---------\n");
	printf("---------002---------\n");
	goto here;//注意这里是分号
	printf("---------003---------\n");
	printf("---------004---------\n");
	printf("---------005---------\n");
here://注意这里是冒号
	printf("---------006---------\n");
	printf("---------007---------\n");
}

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

逆向基础之C语言 第一篇_第38张图片
逆向基础之C语言 第一篇_第39张图片

作业:
重复输入1~7的数字判断星期几,注意输入0,就结束程序,如果是0或1 ~7以外的数,请提示“请重新输入有效数值“

总结:
for 和 while 我们如何选择呢?
for和while低位一样
建议:
如果循环次数是确定的,建议选择for。
如果循环次数不确定,只知道退出的条件,建议选择while


知识点4【数组的概述】

逆向基础之C语言 第一篇_第40张图片

知识点5【一维数值数组】

1、数组的定义

需求:请定义一个数组,该数组有十个元素,每个元素为int类型

在定义的时候:(按照步骤来,因为以后的数组很复杂)
a、arr[ ] arr和中括号结合时就是数组
b、将确定的元素的个数放入中括号中
c、用元素的类型定义一个普通变量
d、从上往下整体替换(变量名)

arr[10]
int num;
用arr[10]整体替换num——》int arr[10];

需求:请定义一个数组,该数组有十个元素,每个元素为地址,每个地址为int变量的地址。

arr[10]
*p //每个元素为地址,地址就是指针
int num;
//从上往下依次替换
arr[10]替换p——》 * arr[10]
星花 * arr [10]替换num——》int * arr [10]

注意:
1、数组名arr不能和其他变量名同名
2、数组的元素下标是从0开始的:0~9
3、数组的元素分别是:arr[0]、arr[1]…arr[9],如果访问arr[10]数组越界
4、数组的元素等价于普通变量
5、在定义数组的时候,中括号里面的值必须为常量,不能是变量。

案例1:遍历数组

	//局部数组(变量)如果不初始化,内容也是不确定的
	int arr[10];

	//遍历数组——》for循环
	int i=0;
	for(i=0;i<10;i++)
	{
     
		printf("%d ",arr[i]);//输出的都是不确定的值
	}
	printf("\n");

案例2:数组的初始化

初始化:定义的时候给变量或者数组元素赋值的动作,叫做初始化

全部初始化:
int arr[5]={
     10,20,30,40,50};
//如果是全部初始化,数组元素的个数可以省略,实际元素个数由初始化个数决定
int arr[]={
     10,20,30,40,50};//少用

//错误演示:
int arr[];//没有初始化,不知道开辟多大空间
部分初始化:
int arr[5]={
     10,20,30};//部分初始化,未被初始化的部分补0(重要)

//初始化数字常见操作(将数组所有元素清零)
int arr[5]={
     0};//初始化arr[0]=0,未被初始化部分自动补0
int arr[5]={
     2};//2 0 0 0 0
拓展:特殊初始化
//了解
int arr[5]={
     [2]=3,[4]=7}; // 0 0 3 0 7
//[2]=3  将数组的第二个元素 初始化为 3
//[4]=7 将数组的第四个元素 初始化为 7

2、数组的空间大小(重要)

	//arr数组名 作为数组类型  代表的是数组空间的总大小
	int arr[5]={
     10,20,30,40,50};
	int n=0;
	printf("数组的总大小:%d\n",sizeof(arr));//20个字节
	//数组的总大小=元素的个数*每个元素的大小
	//    20      =    5     *     4

	//数组元素的大小 arr[0]是数组第0个元素,数组的每个元素都是相同类型
	printf("数组元素的大小=%d\n",sizeof(arr[0]));//4

	//数组元素的个数=数组的总大小 / 每个元素的大小
	n= sizeof(arr) / sizeof(arr[0]);
	printf("数组元素的个数为:%d\n",n)//5;

遍历数组

	//arr数组名 作为数组类型  代表的是数组空间的总大小
	int arr[5]={
     10,20,30,40,50};
	int n= sizeof(arr) / sizeof(arr[0]);//是求数组的元素个数
	int i=0;
	//遍历数组
	for(i=0;i<n;i++)
	{
     
		printf("%d ",arr[i]);
	}
	printf("\n");

逆向基础之C语言 第一篇_第41张图片

案例:

	int arr1[5]={
     1,2,3};
	int arr2[]={
     1,2,3};
	char arr3[5]={
     1,2,3};
	int arr4[]={
     [5]=7};//VC6.0不支持

	printf("%d\n",sizeof(arr1));//20
	printf("%d\n",sizeof(arr2));//12
	printf("%d\n",sizeof(arr3));//5
	printf("%d\n",sizeof(arr4));//6*4=24

3、数组元素的操作

数组元素等价于普通变量

	int arr[5]={
     1,2,3,4,5};
	int n=sizeof(arr)/sizeof(arr[0]);
	int i=0;
	//给数组元素赋值
	arr[0]=100;
	//num++
	arr[1]++; //arr[1]=arr[1]+1;

	//scanf("%d",&num)
	scanf("%d",&arr[2]);

	for(i=0;i<n;i++)
	{
     
		printf("%d ",arr[i]);
	}
	printf("\n");

案例:定义一个数组 5个元素,每个元素int类型,获取键盘输入

	//数值数组只能逐个元素操作  不能整体操作
	int arr[5]={
     0};
	int n=sizeof(arr)/sizeof(arr[0]);
	int i=0;

	printf("请输入%d个int型数据\n",n);
	for(i=0;i<=n;i++)
	{
     
		scanf("%d",&arr[i]);//中括号[]的优先级高于&
		//&arr[i] 表示取第i个元素的地址
	}
	for(i=0;i<n;i++)
	{
     
		printf("%d ",arr[i]);//arr[i]访问数组的第i个元素 
	}

作业:定义一个数组 5个元素,每个元素int类型,获取键盘输入,请求出数组的最大值,最小值,平均值。


知识点6【二维数组】

1、二维数组的定义

逆向基础之C语言 第一篇_第42张图片
逆向基础之C语言 第一篇_第43张图片
二维数组也叫数组的数组。

int arr[3][4];
//第一个中括号里面的值表示行数
//第二个中括号里面的值表示列数
//arr数组有三行四列
//三行:0~2行
//四列:0~3列

逆向基础之C语言 第一篇_第44张图片

二维数组的遍历:

//先写内层循环,再把它粘贴到外层循环中去
int i=0;
for(i=0;i<3;i++)
{
     
	//遍历第i行
	int j=0;
	for(j=0;j<4;j++)
	{
     
		printf("%d ",arr[i][j])
	}	
}

案例:

#include
void test01()
{
     
	int arr[3][4];//局部数组不初始化,内容不确定
	int i=0,j=0;
	
	for(i=0;i<3;i++)//行的变化
	{
     
		//先写内层循环,再写外层循环,将内层循环复制到外层循环
		for(j=0;j<4;j++)//列的变化
		{
     
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}
int main(int argc,char *argv[])
{
     
	test01();
	return 0;
}

逆向基础之C语言 第一篇_第45张图片

2、二维数组的初始化

分段初始化:用大括号里面的大括号,明确的表示一行

int arr[3][4]={
     {
     1,2,3,4},{
     5,6,7,8},{
     9,10,11,12}};

在这里插入图片描述

连续初始化:放满一行才能放下一行

int arr[3][4]={
     1,2,3,4,5,6,7,8,9,10,11,12};

案例:

	int arr1[3][4]={
     {
     1,2},{
     3},{
     4,5}};
	int arr2[3][4]={
     1,2,3,4,5};

逆向基础之C语言 第一篇_第46张图片

04_数组与函数

知识点1 【作业讲解】

作业:定义int arr[3][4]的二维数组,获取键盘输入,并求出每一行的平均值。

//作业:定义int arr[3][4]的二维数组,获取键盘输入,并求出每一行的平均值。
void test03()
{
     
	int arr[3][4]={
     0};//将整个数组初始化为0
	//获取键盘输入
	int i=0,j=0;
	for(i=0;i<3;i++)
	{
     
		for(j=0;j<4;j++)
		{
     
			scanf("%d",&arr[i][j]);
		}
	}

	for(i=0;i<3;i++)
	{
     
		//求每一行的平均值
		float sum=0;	
		for(j=0;j<4;j++)
		{
     
			sum+= arr[i][j];
		}
		printf("第%d行的平均值为%.2f\n",i,sum/4.0);

	}

运行结果:
逆向基础之C语言 第一篇_第47张图片

案例:求每一列的平均值

void test03()
{
     
	int arr[3][4]={
     0};//将整个数组初始化为0
	//获取键盘输入
	int i=0,j=0;
	for(i=0;i<3;i++)
	{
     
		for(j=0;j<4;j++)
		{
     
			scanf("%d",&arr[i][j]);
		}
	}

	for(i=0;i<3;i++)
	{
     
		//求每一行的平均值
		float sum=0;	
		for(j=0;j<4;j++)
		{
     
			sum+= arr[i][j];
		}
		printf("第%d行的平均值为%.2f\n",i,sum/4.0);

	}


	for(j=0;j<4;j++)
	{
     
		int sum=0;
		for(i=0;i<3;i++)
		{
     
			sum+=arr[i][j];
		}
		printf("第%d列的平均值为%d\n",j,sum/3);
	}
	// 计算第0列

}

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

逆向基础之C语言 第一篇_第48张图片

知识点2 【字符数组】(重要)

字符数组:本质是数组,只是数组的美国元素是字符。

//定义一个数组有五个元素,每个元素为char
arr[5]
char ch;
从上往下替换======>
char arr[5];

字符数组的初始化

//初始化:逐个字符初始化(不推荐)
char str[6]={
     'h','e','l','l','o'};
//str[0]== 'h'的ASCII码值

//初始化:以字符串的形式初始化(推荐)
char str[6]="hello";//初始化更加简洁

字符数组的遍历

char str[6]="hello";
	//逐个字符遍历(不推荐)
	int i=0;
	for(i=0;i<6;i++)
	{
     
		printf("%c",str[i]);
	}
	printf("\n---------\n");

	//字符数组可以整体遍历(推荐)
	printf("%s\n",str);//%s: 从给定字符串的首元素地址开始逐个字符遍历,直到遇到\0结束
	//%s加数组名str

运行结果:
逆向基础之C语言 第一篇_第49张图片
%s:只要告诉字符串第0个元素的地址,那么就可以逐个元素打印,直到遇到\0结束

非常重要:一维数组名代表的是 数组第0个元素的地址。(必须记住)

	char str[6]="hello";
	//%p是十六进制输出地址
	printf("第0个元素的地址:%p\n",&str[0]);
	printf("数组名:%p\n",str);

逆向基础之C语言 第一篇_第50张图片

逐个字符初始化和字符串初始化的区别

	char str1[]={
     'h','e','h','e'};//逐个元素初始化,系统不会给它添加‘\0’
	char str2[]="hehe";//以字符串初始化,系统会给字符串添加'\0'
	//以上情况的前提条件是中括号里面没给数组长度
	
	//开辟的空间大小
	printf("sizeof(str1)=%d\n",sizeof(str1));//4
	printf("sizeof(str2)=%d\n",sizeof(str2));//5

逆向基础之C语言 第一篇_第51张图片
逆向基础之C语言 第一篇_第52张图片

数组中括号里面没有给定元素的个数那么元素的实际个数由初始化情况决定

%s输出的内容
%s的作用是:从数组的第0个元素开始,逐个元素输出,直到遇到\0结束

	//%s输出的内容
	//%s的作用是:从数组的第0个元素开始,逐个元素输出,直到遇到\0结束
	printf("str1=##%s##\n",str1);
	printf("str2=##%s##\n",str2);

★★★运行结果:
在这里插入图片描述

字符数组获取键盘的字符串

定义一个字符数组,有足够大的空间

	char buf[128]="";
	printf("请输入一个字符串\n");
	scanf("%s",buf);//不能获取带空格的字符
	printf("buf=%s\n",buf);

运行结果
逆向基础之C语言 第一篇_第53张图片

scanf和%s使用的时候有个缺点,遇到空格会结束输入

逆向基础之C语言 第一篇_第54张图片
解决方法:用gets()——》把键盘输入的大小全搬给你
案例:

	char buf[128]="";
	printf("请输入一个字符串\n");
	//scanf("%s",buf);//不能获取带空格的字符串
	gets(buf);//能获取带空格的字符串  缺点:不会管buf的大小,容易造成内存污染
	printf("buf=%s\n",buf);

gets()缺点:获取键盘输入的时候,不会管buf的大小,容易造成内存污染

逆向基础之C语言 第一篇_第55张图片

fgets():既可以获取带空格的字符串,也可以保证buf的不越界

#include
char *fgets(char *s, int size, FILE *stream)
//s表示存放字符串的空间地址
//size表示能提取字符串的最大长度 size-1(留一个给'\0')
//stream    stdin表示标准输入设备
返回值:就是获取到的字符串的首元素地址
案例:
void test07()
{
     
	char buf[10]="";
	printf("请输入一个字符串\n");
	fgets(buf,sizeof(buf),stdin);
	printf("buf=%s\n",buf);
}

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

逆向基础之C语言 第一篇_第56张图片
逆向基础之C语言 第一篇_第57张图片

提高案例:字符串的大小写的转换
	//'a' 97   'b' 98  'c'99 ~ 'z'122
	//'A' 65   'B' 66  'C'67 ~ 'Z'90
	//规律:小写字母的ASCII值比大写字母的ASCII值相差32
	//printf("%d %d\n",'a','z');
	//printf("%d %d\n",'A','Z');

	char ch='a';
	//小写变大写  -32
	ch=ch-32; //其实也可以写:ch=ch-('a'-'A');
	printf("ch=%c\n",ch);

	//大写变小写  +32
	ch=ch+32;//其实也可以写:ch=ch+('a'-'A');
	printf("ch=%c\n",ch);

作业:键盘获取一个字符串,大小自定义,将字符串的大写字母变小写,小写字母变大写,其他符号保持不变
思路如下:
1、获取一个字符串
2、用for循环将字符串的逐个字符进行大小写转换


知识点3【二维字符数组】(了解)

一维字符数组是存放多个字符的

二维字符数组是存放多个字符串的 ,每个字符串占一行

//不管是数值还是字符的二维数组,在初始化的时候是库省略行标的,行数又具体初始化决定
char str[3][16]={
     "hehe","haha","heihei"};//要保证每个字符串小于16个字节
str[0]="hehe"; str[1]="haha";  str[2]="heihei";

逆向基础之C语言 第一篇_第58张图片

	char str[3][16]={
     "hehe","haha","heihei"};
	//对整体字符串输出,只需要使用行标
	printf("%s\n",str[0]);//hehe
	printf("%s\n",str[1]);//haha
	printf("%s\n",str[2]);//heihei

	//输出的是 字符串中某个字符  必须用行标和列表
	printf("%c\n",str[1][3]);//a

逆向基础之C语言 第一篇_第59张图片

二维字符数组获取键盘输入的字符串:

应用场景:当键盘需要输入多个独立的字符串,用户必须单独存储好,请选择二维字符数组

输入的字符串个数,决定了二维数组的行数输入字符串中最大长度,决定了二维字符数组的列数

	//hehe haha xixi heihei 
	char buf[4][16]={
     ""};//二维字符数组初始化为0
	int i=0;

	//获取键盘的字符串
	for(i=0;i<4;i++)
	{
     
		scanf("%s",buf[i]);//buf[i]已经代表是第i行的首元素地址,所以这时候不要取地址
	}

	//输出键盘的字符串
	for(i=0;i<4;i++)
	{
     
		printf("buf[%d]=%s\n",i,buf[i]);
	}

逆向基础之C语言 第一篇_第60张图片
作业:
1、键盘输入十个int数据,对其从小到大排序
2、键盘输入一个字符串“abcdef”进行前后的颠倒(逆置)——》“fedcba”

知识点4 【函数概述】

1、函数的定义:实现函数的功能,确定函数体、返回值、形参类型。让函数存在
2、函数的声明:不是实现函数功能,仅仅是说明函数该函数有返回值类型,形参类型,函数名。
3、函数的调用:函数的执行

1、函数的定义

// 返回值类型:函数将来返回值的类型
//函数名:函数的入口地址
//形参类型:函数外部数据 传递到函数内部的桥梁
//函数体:具体的函数功能代码
返回值类型  函数名(形参类型  形参)
{
     
	函数体;
}

2、函数的声明

返回值类型  函数名(形参类型  形参);

3、函数的调用

//函数外部的实际数据
函数名(实参);//没有函数返回值类型

案例:

#include
//函数声明(必须在函数调用的前方声明才有作用)//编译器是从上往下执行的
void my_fun();//函数声明:告诉编译器,该函数存在,请通过编译。

int main(int argc,char *argv[])
{
     
	//函数的调用:函数名+()
	my_fun();
	return 0;
}

//函数的定义
void my_fun()
{
     
	printf("my fun\n");
	return;//void没有返回值,return后面什么都不写
	//return功能:1、返回函数结果   2、结束函数
}

逆向基础之C语言 第一篇_第61张图片

可以省略函数声明:函数的调用在函数定义的下方,就可以省略函数声明

#include
//函数声明(必须在函数调用的前方声明才有作用)//编译器是从上往下执行的
//void my_fun();//函数声明:告诉编译器,该函数存在,请通过编译。

//函数的定义
void my_fun()
{
     
	printf("my fun\n");
	return;//void没有返回值,return后面什么都不写
}

int main(int argc,char *argv[])
{
     
	//函数的调用:函数名+()
	my_fun();
	return 0;
}

建议:
往往建议将主函数写在上方,将函数定义写在下方。(通过主函数,一眼就能看出大致框架)

知识点5 【函数的参数】

1、如果函数的形参什么都不写 在调用的时候可以传实参,只是实参得不到使用

#include

void my_fun(10,20);

int main(int argc,char *argv[])
{
     

	my_fun();
	return 0;
}

//如果函数的形参什么都不写 在调用的时候可以传实参,只是实参得不到使用
void my_fun()
{
     
	printf("my fun\n");
	return;
}

2、如果函数没有参数,请将形参写成void

#include

void my_fun(void);

int main(int argc,char *argv[])
{
     
	//my_fun(10,20);//error

	my_fun();
	return 0;
}

//如果函数的参数为void,在调用的时候就不要给函数传递实参
void my_fun(void)
{
     
	printf("my fun\n");
	return;
}

3、函数参数的传递

逆向基础之C语言 第一篇_第62张图片

你可能感兴趣的:(Programming,in,C)