C语言复习笔记——适合一些需要复习和进阶c语言的朋友,这只能说是个概要。当然,如果朋友你看到某个内容能联想到知识点,那你应该是掌握了的,你可以尝试找找其他的文章去深入理解下,如有错误和疑问可以直接指出

C语言基本数据类型

C语言常量

  • 什么是常量: 固定的值

  • 常量的分类:

    • 整形常量
    //整数
    //1,2,3,4  十进制数
    //特殊格式的整数
    //0123   八进制:0开头
    //08---> 不算
    //0x123  十六进制整数: 0x开头
    //0b10111 2019以下的编译器不支持
    //特殊后缀的整数
    //1u     无符号整数: 没有负数的意思
    //1L     长整形
    
    • 浮点常量
    //实数(包含整数包含小数)
    //1.333  默认是双精度(double)浮点型常量
    //1.33f  单精度浮点常量
    //特殊的表示方式
    //科学计数法
    //1234E-5或者1234e-5,即使0.12345
    //	.E-4;  错误的写法
    
    • 字符常量

      • 转义字符: 是具有特定含义字符,一般是由\和特殊字符组成的具有特殊含义的字符

        在这里插入图片描述

    //字符边界符 ''
    'A'
    //一般情况只有一个字符  
    //转义字符
    //'\t'
    //'\n'
    
    • 字符串
      • 字符串边界符 “”
      • 每一个字符串都会存在看不见的结束符(‘\0’);

C语言变量

  • 什么是变量,可以改变量(其实在C语言充当数据容器的作用)

  • 定义变量的语法: 类型 标识符;

    • 类型

    • 标识符: 就是一个名字

      • 起名字: 见名知意
      • 标识符不能用数字当做开头
      • 标识符不能是C语言中的关键字(if,case,switch,int)
      • 标识符大小是区分
    • 起名字的方式

      • 帕斯卡:大驼峰 PlayMusicByUser —>MFC
      • 驼峰 :playMusicByUser—> 使用这一种命名方式
      • 匈牙利: 特定前缀组成的命名方式
        • g_ : 全局变量 g_iNum;
        • c_: 常量 c_iNum;
    • 数据类型

      • 基本的数据类型

        • int : 整数类型
        • float: 单精度浮点类型
        • double: 双精度浮点类型
        • char: 字符类型
      • 修饰词

        • short(短)和 long(长): 体现数据占用内存(容量的大小)的基础上)
        short iNum=0;
        short int sNum=0;
        long lNum;
        long int liNum;
        //有修饰和没有修饰词是两种不同的数据类型
        //什么是用短的: 想要节省内存
        //什么是用长的: 数据超出了原来表示范围,用long
        
        • unsigned(无符号) 和signed(有符号)
        //无符号:表示不能表示负数
        //一般默认的类型,表示的有符号
        //年龄没有负数-->这种需求
        
        • const(常属性)
          • 常属性的变量定义的时候必须初始化
          • 常属性的变量不能被修改
          • 小提示: 一个程序不能大量出现常量的数—>幻数
      • 注意点:

        • short 和long 只能修饰整形
        • unsigned(无符号) 和signed(有符号) 修饰整数数和字符

    auto/extern/static (后续再说)

进制与数据存储

其他进制与进制转换

  • 十进制: 1 2 3 4

  • 二进制: 3: 0:0 1:1 10:2 11:3

    • 二进制转十进制: 短除法

      在这里插入图片描述

  • 八进制:8: 10

    在这里插入图片描述

  • 十六进制

    在这里插入图片描述

在这里插入图片描述

  • 转换的小知识: 二进制转八进制 在十六进制
    • 16:10000 8: 1000 4: 100 2:10 1:1
    • 三个二进制表示一个八进制位
    • 四二进制位一个十六进制位

在这里插入图片描述

  • 其他进制转十进制: 加权法

在这里插入图片描述

二进制与数据存储的关系

  • 二进制与存储单位

    存储单位表示含义
    bit :位一个二进制位,能表示的数字只有0和1
    byte:字节1B=8b,8个二进制位表示一个字节
    KB: 千字节1KB=1024B =2^10
    MB:兆字节1MB=1024KB=2^10KB
    GB: 吉字节1GB=1024MB=2^10MB
    TB: 太字节1TB=1024Gb=2^10GB
  • 数据的二进制存储

    • 所有数据在计算中都是用补码来存储
    • 补码: 反码+1
    • 反码: 原码取反(但是符号位不变)
    • 原码: 负数: 引入了符号位,用第一位表示整数,0:正数,1表示负数
    • 正数的二进制码 都是三码合一
    正数:3:(三码合一)   
    原码:0000 0000 0000 0000 0000 0000 0000 0011 
    反码:0000 0000 0000 0000 0000 0000 0000 0011 
    补码:0000 0000 0000 0000 0000 0000 0000 0011 
    负数:-3
    原码:1000 0000 0000 0000 0000 0000 0000 0011   //引入符号,最高位表示位
    反码:1111 1111 1111 1111 1111 1111 1111 1100   //原码取反(但是符号位不变)
    补码:1111 1111 1111 1111 1111 1111 1111 1101   //反码+1
    //3-3
    0000 0000 0000 0000 0000 0000 0000 0011    
    1111 1111 1111 1111 1111 1111 1111 1101   
    0000 0000 0000 0000 0000 0000 0000 0000        //进制位处理(寄存器有标志位处理)
    //注意点: 补码要算出十进制数,必须先转换为原码,再去用加权法
    
  • C语言数据的范围

在这里插入图片描述

范围计算: 以char类型为例

char: 1—> 0000 0000

最大值: 0111 1111

1000 0000-1

2^7-1=128-1 =127

-2^7=-128

int: 32–> -231-231-1

C语言基本输入和输出

printf函数

  • #include <stdio.h> C语言标准输入输出头文件

  • printf做原样打印

    //函数
    //printf("你要打印的内容");
    
  • 转义字符

    常用的转义字符效果
    \n换行
    \r换行(当前行首),判断按键是否是回车键
    \t水平制表

    制表的原理: 通过控制数据的输出宽度,不足填充空格

  • 打印特殊符号

    • 通过\加上特殊符号打印符号
  • 数据的输出

    • 格式控制字符: 占位符
    int  %d
    char %c
    float %f
    double %lf
    字符串  %s
    unsigned int %u
    printf("a=%d,b=%c",1,'A');
    a=%d,b=%c
       1,  'A'
    a=1,b=A
    //一般情况: 占位符的个数是要和后面数据个数是相同
    
    • 浮点型的格式问题
    %+a.bf    
    %-a.blf
    a:输出数据的宽度,不足补空格
    b:小数位
    +: 右对齐 ,不写,默认是右对齐
    -: 左对齐
    

scanf函数

  • scanf基本用法
    • &(shif+7键按出来)取地址符
    • scanf工作原理
      • scanf(“格式控制字符”,“变量的地址”);
      • 输入的格式控制字符不需要转义字符
    • error C4996: ‘scanf’: This function or variable may be unsafe. Consider using scanf_s instead
    • 项目属性–>SDL改为否
  • 字符输入
    • 清除缓冲区—> 在字符或者字符串输入前 做了输入,就需要清空
  • scanf_s: vs给scanf做的增强版本
    • 输入数字类是没什么区别
    • 输入字符类或者字符串必须要加入长度
    • 其他编译器没有scanf_s 只有scanf
  • scanf一次输入多个不同类型的数据
#include <stdio.h>
int main() 
{
	//No.4 不同数据类型输入
	//数字和字符组合在一起
	int iNum;
	char xNum;
	double dNum;
	printf("input int,char,double:");
	//scanf自动区分数据,不需要认为区分
	scanf("%d %c%lf", &iNum, &xNum, &dNum);
	printf("iNum=%d,xNum=%c,dNum=%lf\n", iNum, xNum, dNum);
	printf("input int,char,double:");
	//%d,%c,%lf 
	//scanf("%d,%c,%lf", &iNum, &xNum, &dNum);
	//printf("iNum=%d,xNum=%c,dNum=%lf\n", iNum, xNum, dNum);
	scanf_s("%d %c%lf", &iNum, &xNum,1, &dNum);
	printf("iNum=%d,xNum=%c,dNum=%lf\n", iNum, xNum, dNum);

	//No.1 输入单个数据
	printf("input a num:");
	//习惯:定义变量的时候给变量初始化(赋初始值)
	int num = 0;       //定义变量赋值为0
	int result = 0;
	result=scanf("%d", &num); //等待键盘输入
	printf("num=%d\n", num);
	//No.2 输入多个数据
	int a, b;
	printf("input a,b:");
	result=scanf("%d%d", &a, &b);
	printf("a=%d,b=%d\n",a,b);
	//scanf("%d\n", &a);  没有这种学法
	float fNum;
	//scanf("%.3f", &fNum); 没有这种学法
	//No.3 字符输入
	char cNum;
	char x;
	printf("input a char:");
	//getchar();			    //----> 常用
	scanf("%c", &x);			//清除缓冲区的\n x等于\n
	//fflush(stdin);			//被淘汰
	//setbuf(stdin);			//会影响文件操作
	//这个两个等效上面scanf的作用
	scanf("%c", &cNum);
	printf("cNum=%c\n", cNum);
	printf("input a char:");
	scanf("%c", &x);
	scanf_s("%c", &cNum, 1);	//长度一般用变量能够存储的数据长度
	printf("cNum=%c\n", cNum);
	//字符串--字符数组;
	return 0;
}

字符处理函数

getchar函数

  • 输入一个字符 ,getchar() 表示输入的字符
  • #include <stdio.h>

putchar函数

  • 输出一个字符 ,putchar(‘A’) 要输出的字符放在括号中
  • #include <stdio.h>

getch函数

  • 输入一个字符,getch() 表示输入的字符,不需要回车确认输入
  • #include <conio.h>
  • 在vs中,getch()要改为 _getch()

ASCII码的知识

  • ASCII码 做整数和字符的转换

在这里插入图片描述

C语言运算符和表达式

运算的基本概念

  • 左值和右值以及操作数的概念

    • a=1 a:左值 1:右值
      • error C2166: 左值指定 const 对象 左值一般是变量
      • 右值: 没有什么太多要求
    • 操作:=需要两个数字运算符,操作数2
    • 单目,双目, 三目
  • 优先级:算法: 算表达式的顺序(先算*/ 再算+ -)

  • 结合性: 读法: a=1; 正确应该是把1赋值给a

    在这里插入图片描述

逗号–>赋值–>逻辑–>条件–>算术–>位–>括号

基本运算符

  • 赋值运算符
a=1;
  • 算术运算符
//+ -  
//* /  				 在写表达式的时候乘法不能省略
//2(3+4)
int a=2*(3+4);		// 在写表达式的时候乘法不能省略
//除法
1/3=0;				//当除法的两遍都是整数的时候,会自动取整
//1/2+2/3+3/4;
double result=1/2.0+2/3.0+3/4.0
//取余:%
int num=1%3         //1  x%n  [0,n-1]--->随机数
//余数的符号
num=1%-2;			//1 余数只和被取余数的正负有关    
  • 复合赋值运算符
int a=1;
a+=2;    //a=a+2
a/=2;    //a=a/2
a*=2;    //a=a*2
a=2;
a*=1+2*3; //表达式右边有没有括号,都没关系,都是一个整体
//a=a*(1+2*3); //14
  • 条件运算符
    • 条件表达式只有两个结果: 0(表示不成立) 或者 1(成立)
    • 在计算机中非零值表示成立,只有0(\0)或者NULL 表示不成立
    • 不存在连续操作,例如描述 a大于1并且小于2 1<a<2 错误
      • 1<a<2是永远成立的 1<a 结果[0,1] , [0,1]<2 永远成立
//> < 
//不等于 !=  中间没空格,有空格就是错误
//等于 ==
//>=  <=  	
  • 逻辑运算符
&&: 逻辑与运算--->并且
||:逻辑或运算--->或者
!: 否定      --->成立变成不成立,不成立变成成立
//!3  --> 0
//!0  --> 1
aba&&ba||b
0000
1001
1111
0101

综上:

a&&b : a和b都成立它才能成立 ,其他情况都是不成立

a||b : a和b都不成立它才能不成立 ,其他情况都是成立

逻辑与运算和逻辑或运算都会存在短路现象(计算机偷懒现象)

int a = 2;
a > 1 || (a = 4);  //	a > 1成立 所以a > 1 || (a = 4)成立
a < 1 && (a = 5);  //   a < 1不成立 所以a < 1 && (a = 5)不成立
printf("%d\n", a);

位运算符

目前: 学会运算符方式即可

&: 按位与
|: 安位或运算
~: 按位取反
^: 异或
>>:左移
<<: 右移

在这里插入图片描述

在这里插入图片描述

左移: 正负数都是右边补0

右移: 正数: 左边补0 负数: 左边补1 不要去纠结为什么,就是一个前辈发现的计算规则,如此而已

特殊运算符

  • ++ –
int a=1;
a++;   //后置   先其他运算,改变自己
++a;   //前置   先改变自己,在做运算
//a++  a=a+1;
a--;
--a;
//a--  a=a-1
int a = 1;
int b = 1;
int result;
result = a++;  //result=a ,在a=a+1
printf("result=%d\ta=%d\n", result, a);  //1 2
result = ++b;  //先b=b+1  在做 result=b
printf("result=%d\tb=%d\n", result, b);  //2 2
a = 1;
result = 3 * a++;
printf("result=%d\ta=%d\n", result, a);
  • sizeof
    • 统计类型占用内存(字节)
    • 他是再编译期完成的
  • ?:
表达式1?表达式2:表达式3
1成立  2
1不成立  3
  • 逗号运算符
    • 有效值 是最右边的值

内存共享问题

#include <stdio.h>
int main() 
{
	int a = 1;
	int resultFirst = a++ + a++ + ++a + ++a;  //a:3
	//只看前置,不看后置,再看几部分
	// 3+3+3+3
	//resultFirst:3*4=12  a:5
	//只看前置:a++ + a++ + ++a + ++a 两个前置:a要加两次,运算时a的值3
	//再看几部分:a++ + a++ + ++a + ++a 有四部分: 4
	printf("result=%d\ta=%d\n", resultFirst, a); //12 5
	int b = 1;
	int resultSecond = b++ * b++ * ++b * b++ * b++;
	//自己会算就可以
	//b运算时候: b=2
	//resultSecond:2^5;
	printf("result=%d\tb=%d\n", resultSecond, b); //32 6
	int c = 1;
	int resultThree = c++ * c-- * c++ * ++c * --c * c++;
	printf("result=%d\tc=%d\n", resultThree,c);
	//切记: 不要自己再自己的代码里面写这样的东西
	return 0;
}

C语言分支结构

if语句

if(表达式)   //如果
{
    //满足条件执行
}	//尽量带上这个{},不带只管理一条语句
	//No.1 基本if语句
	if ("我很帅")			//非零表示成立
	{
		printf("那么我很帅!\n");
	}
	int a = 1;
	if (a = 0)   //赋值语句充当条件,最终是赋的值充当条件 if(0)
	{
		printf("赋值语句!\n");
	}
	//求最大值:
	int b;
	int c;
	scanf_s("%d%d%d", &a, &b, &c);
	//No.2 if处理最大值问题
	int max = a;
	if (max < b)
	{
		max = b;
	}
	if (max < c) 
	{
		max = c;
	}
	printf("Max=%d\n", max);
	//No.3 if处理多个条件使用逻辑运算符&& ||
	//判断一个数字是否能够被3和5整除
	int num = 45;
	if (num % 3 == 0 && num % 5 == 0) 
	{
		printf("满足条件的数字\n");
	}
	//判断一个数字是否能够被3但是不能被5整除
	//num % 3 == 0 && num % 5 != 0
	//判断一个数字能够被3或者被5整除
	//num % 3 == 0 || num % 5 == 0

if _else语句

//复合语句
if(表达式)
{
	//条件成立执行这里  
    printf("1");
}
else
{
	//条件不成立执行这里
    printf("1");
}

if_else if _else

//条件细化
//条件的逐步筛选
if(表达式1)
{
    //1.
}
else if(表达式2)
{
    //2.
}
else if(表达式3)
{
	//3.    
}
.....
else
{
    //
}
//整个语句一条复合语句,只会做一次成功的比较,执行一个地方

switch语句

switch(表达式1)
{
	case 常量1//表达式1 和常量1比较
        语句1case 常量2:
         语句2case 常量3:
         语句3default:
         其他情况执行的地方;    
}
//1.switch执行过程
//2.case后面必须是常量,一般写的数字,或者字符,不能写字符串
//3.switch语句只做一次成功的比较,后面的语句不需要比较,都会一次执行
//4.default的位置是随便放的,一般是放在最小面的
//5.switch语句不执行case和default以外的其他语句

switch常用形态

菜单跳转

	//1.菜单交互
	while (1) 
	{
		//菜单绘制
		printf("------【xxx管理系统】------\n");
		printf("\t0.退出系统\n");
		printf("\t1.录入信息\n");
		printf("\t2.删除信息\n");
		printf("\t3.查找信息\n");
		printf("-------------------------\n");
		//菜单交互
		int key = 0;
		scanf_s("%d", &key);
		switch (key) 
		{
		case 0:
			printf("退出系统成功!\n");
			system("pause");		//防止闪屏 ,等待按键继续
			exit(0);		//关闭整个程序;
			break;
		case 1:
			printf("---录入信息!\n");
			break;
		case 2:
			printf("---删除信息!\n");
			break;
		case 3:
			printf("---查找信息!\n");
			break;
		default:
			printf("---输入错误,重新输入\n");
			break;
		}
		system("pause");		//防止闪屏 ,等待按键继续
		system("cls");			//清屏
	}

按键交互

#include <stdio.h>
#include <stdlib.h>		//system函数头文件
#include <conio.h>
int main() 
{
	while (1) 
	{
		printf("一个貌美如花的小姐姐朝你走来!---\n");
		char userkey = _getch();  //不可见的按键交互,不需要回车确认输入
		switch (userkey) 
		{
			//相同的case处理方案可以把所有case写在一起
		case 'w':
		case 'W':
		case 72:
			printf("你朝-->上方-->逃\n");
			break;
		case 'A':
		case 'a':
		case 75:	
			printf("你朝-->左边-->逃\n");
			break;
		case 's':
		case 'S':
		case 80:
			printf("你朝-->下方-->逃\n");
			break;
		case 'd':
		case 'D':
		case 77:
			printf("你朝-->右边-->逃\n");
			break;
		}
	}
	return 0;
}

变量的作用域和生命周期

  • 作用域: 使用范围
    • 局部变量
    • 全局变量
    • 外部变量:extern
  • 生命周期: 变量产生到死亡的时间段
    • 静态变量
    • 自动变量(auto)
#include <stdio.h>
int g_num;					//全局变量
extern int e_num;			//告诉当前文件,该变量是外部文件中的变量
//全局变量没有做初始化,默认0
//全局变量不易过多,因为生命周期,只有当程序关闭 才结束
int main() 
{
	//静态变量养成初始化的习惯
	static  int sNum=1;		
	//会存放上一次执行结果,初始化操作只会做一次
	printf("%d\n", sNum);
	while (1) 
	{
		static  int sCount = 1;   //只会执行
		int count = 1;			  //每一次循环都执行
		sCount++;
		count++;
		printf("sCount=%d,count=%d\n", sCount,count);
	}
	printf("%d\n", g_num);
	printf("%d\n", e_num);
	int iNum = 0;				//局部变量
	{
		int number = 0;			//局部变量
		printf("%d\n", number);
	}
	int num;
	//error C4700: 使用了未初始化的局部变量“num”
	//printf("%d\n", num);		

	//printf("%d\n", number);  未定义的标识符
	for (int i;0;) 
	{
	}
	//i = 3;  //未定义

	return 0;
}

C语言循环结构

基本循环

  • while循环
  • do-while循环
  • for循环
while(表达式)
{
 	//循环体	
    //.....
    //循环条件的改变-->朝着不满足条件方向走
}
//无论条件成立与否,至少执行一次
do
{
    
}while(表达式);
//for用的多一点
for(表达式1;表达式2;表达式3)
{
	循环体4;    
}
//1  2 4 3  2 4 3  2 4 3
//表达式3写是循环变量的改变
//表达式2:循环条件
//表达式1:循环条件初始化
//--表达式1定义的变量只能在循环中使用

特殊形态

while(1);   //死循环-->一直在循环
while(getchar()!='\n');//可以用来清空缓冲区的换行
do{}while(0);//不执行
for(;;)

循环跳转语句

break;  跳出整个复合语句
continue: 跳出本次,继续复合语句
goto: 跳转   --->深层循环嵌套的跳出
(千万不要有用goto写这种荒谬的想法)

循环嵌套

#include <stdio.h>
int main() 
{
	//No.1行列关系描述
	//for循环嵌套,原则上来说或任何循环都可以随便嵌套
	for (int i = 0; i < 4; i++)			//i=0    -------->行的变化
	{									//
		for (int j = 0; j < 3; j++)     //j=0  j=1  j=2 --->列的变化
		{								//
			printf("%d%d\t", i, j);		//00   01   02
		}								//
		printf("\n");
	}
	//00 01 02 \n
	//10 11 12 \n
	//20 21 22 \n
	//30 31 32 \n
	/*
			i   j
	****    1   4    5-i
	***     2   3    5-i
	**      3   2    5-i
	*		4   1
	
	*/
	for (int i = 1; i <= 4; i++) 
	{
		for (int j = 1; j <= 5 - i; j++) 
		{
			printf("*");
		}
		printf("\n");	//换行才有行列关系
	}
	/*      i   j
	*       1   1  2*i-1
   ***      2   3  2*i-1
  *****     3   5  2*i-1
 *******    4   7  2*i-1
	*/
	for (int i = 1; i <= 4; i++)
	{
		for (int j = 1; j <= 5 - i; j++)
		{
			printf(" ");
		}
		for (int k = 1; k <= 2 * i - 1; k++) 
		{
			printf("*");
		}
		printf("\n");	//换行才有行列关系
	}
	//No.2 遍历做二次筛选
	//-->给你一个范围
	//-->给你条件
	//100-999  abc  a^3+b^3+c^3=abc;
	//100-999里面的求素数: 只能被1和自身整除
	for (int i = 100; i <= 999; i++)  //每次循环从区间里面拿一个数字出来
	{
		int flag = 1;				  //每个数字的标记都是从1开始
		for (int k = 2; k < i; k++)   //i=100
		{
			if (i % k == 0)			  //100%2==0
			{
				flag = 0;    //设置一个标记位  //flag=0
				break;
			}
		}
		//退出里面循环有两种情况: 
		//第一种,里面if执行了(flag=0)
		//第二种里面一次都没执行(flag=1)
		if (flag == 1)						
		{
			printf("%d\t", i);
		}
	}
	return 0;
}

C语言一维数组

认识数组

  • 如何创建一个数组
//No.1 数组定义语法
//类型 数组名[数组长度]
int  num[3];
float fNum[3];
double dNum[3];
char cNum[3];
//类型: 学过的数据类型
//数组名: 随便起的
//数组长度:根据需求决定数组长度
  • 数组的长相(在内存上的长相)
    • 数组是什么? 多个变量名有规律并且内存连续的变量的集合(白话:多个普通变量)

在这里插入图片描述

  • 数组初始化

    //No.1 创建数组的时候赋值,数组长度和元素个数相同
    int array[3]={1,2,3};   //{}不能省略,arary[0]=1 array[1]=2 array[2]=3
    //No.2 元素个数和长度不同,默认初始化为0
    int array2[3]={1};      //arary2[0]=1 array2[1]=0 array2[2]=0
    //No.3 当初始化的元素完整,数组长度可以省略
    int array3[]={1,2,3};	//自动推断数组长度为3
    //No.4 不存在数组长度为0的数组
    //int array4[];    错误
    //No.5 不能先创建数组在使用{}方式初始化
    //int array[4];
    //array[4]={1,2,3,4};    错误的写法
    
  • 数组的访问

    • 数组名[下标]
    • 一般操作数组都是用循环操作(输入还是打印)

一维数组常规操作

  • 插入—有序插入
#include <stdio.h>
int main() 
{
	int array[100];				//数组能存储的最大元素个数是100个
	int curSize = 0;			//当前元素是0
	int data = 0;
	while (1) 
	{
		printf("input data:");
		scanf_s("%d", &data);
		array[curSize++] = data;  //array[0]=1 curSize=1  
		//做调整
		//在数组里面做下标-1操作或者+1操作,需要考虑下标合理性
		for (int pos = curSize - 1; pos > 0; pos--) 
		{
			if (array[pos] < array[pos - 1]) 
			{ 
				int temp = array[pos];
				array[pos] = array[pos - 1];  //0-1: -1
				array[pos - 1] = temp;
			}
			else 
			{
				break;
			}
		}
		int user = 0;
		while (getchar() != '\n');
		user = getchar();
		if (user == 'n' || user == 'N')  //输入n和N退出 输入状态
			break;
	}
	for (int i = 0; i < curSize; i++) 
	{
		printf("%d\t", array[i]);
	}
	printf("\n");
	return 0;
}
  • 查找+删除(移位)
#include <stdio.h>
int main() 
{
	//查找和删除
	int array[10] = { 10,20,30,40,50,60,70,80,90,100 };
	int curSize = 10;
	//数组的删除叫做伪删除
	int data = 60;			//要找的数据
	//数组的查找
	int pos = -1;			//查找下标一般用-1 ,数组下标会是0
	for (int i = 0; i < curSize; i++) 
	{
		if (array[i] == data) 
		{
			pos = i;       //记录找到元素下标
			break;
		}
	}
	if (pos == -1) 
	{
		printf("没有找到相关元素,无法删除!");
	}
	else 
	{
		//在数组里面做下标-1操作或者+1操作,需要考虑下标合理性
		for (int k = pos; k < curSize-1; k++)   //k=9
		{
			array[k] = array[k + 1];			//array[10]
		}
		curSize--;			//数组真正的删除
	}
	for (int i = 0; i < curSize; i++) 
	{
		printf("%d\t", array[i]);
	}
	printf("\n");
	return 0;
}
  • 查找+修改
#include <stdio.h>
int main() 
{
	//查找和修改
	int array[10] = { 10,20,30,40,50,60,70,80,90,100 };
	int curSize = 10;									//记录当前数组中元素个数
	int data = 60;
	int pos = -1;
	for (int i = 0; i < curSize; i++) 
	{
		if (array[i] == data) 
		{
			pos = i;
			break;
		}
	}
	if (pos == -1) 
	{
		printf("没有找到无法修改!\n");
	}
	else 
	{
		printf("请输入新的值:");
		scanf_s("%d", &array[pos]);
	}
	for (int i = 0; i < curSize; i++) 
	{
		printf("%d\t", array[i]);
	}
	
	return 0;
}
  • 排序—>冒泡排序
#include <stdio.h>
int main() 
{
	//冒泡排序
	int array[10] = { 60,70,80,90,100,10,20,30,40,50 };
	int curSize = 10;
	//排序
	//int count = 0;
	for (int i = 0; i < curSize; i++)          //每个元素都需要一个机会冒泡
	{
		for (int j = 0; j < curSize-1-i; j++)  //描述每个元素冒泡过程
		{
			//从小到大排序
			if (array[j] > array[j + 1])  //不满足规则交换
			{
				int temp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = temp;
				//count++;
			}
		}
	}
	for (int i = 0; i < curSize; i++) 
	{
		printf("%d\t", array[i]);
	}	
	return 0;
}

字符数组和字符串

  • 字符数组和数字类数字处理方案相同的
  • 字符串: 存在一个不可见字符\0
  • 字符串的打印: %s去做打印
    • %s的工作原理: 从首地址打印到\0 所以%s对应的是地址
	//No.1 字符数组
	char array[] = { 'A','B','C' };  //自动推断长度3
	for (int i = 0; i < 3; i++) 
	{
		putchar(array[i]);
	}
	putchar('\n');
	//No.2 字符数组操作字符串
	//字符串有一个不可见字符\0
	char str[] = "ILoveyou";         //自动长度是:9
	char inputstr[] = { 'A','B','C','D','\0','A','B','C' };
	//%s对应的是地址
	printf("%s\n", &inputstr[0]);    //ABCD  &inputstr[0]到\0
	printf("%s\n", &inputstr[2]);	 //CD    &inputstr[2]到\0
	printf("%s\n", &array[0]);		 //ABC烫烫烫烫烫烫烫烫烫烫烫烫烫烫?
	//	0    1   2   3    4   5  6   7
	//{ 'A','B','C','D','\0','A','B','C' }
	// 数组名代表第一个元素的地址
	char output[] = "ILoveyou";
	printf("%s\n", output);   //&output[0]
	int key = 0;
	scanf_s("%d", &key);
	while (getchar() != '\n');		//字符串做输入前做了输入,可以清除缓冲区
	char username[10];
	printf("请输入用户名:");
	//scanf不需要加长度,增强版要加长度 
	//scanf输入不接受带空格的输入
	scanf_s("%s", username,10);  
	printf("name:%s\n", username);
	//字符串输入函数: gets:输入+puts:打印
	char passw[20];
	//所有字符串处理用的数组
	printf("输入密码:");
	while (getchar() != '\n');
	gets_s(passw, 20);		//20一般用的是passw的长度
	puts(passw);			//自带换行
	printf("-----\n");

字符串处理函数

  • strcat: 连接
  • strcpy:拷贝
  • strcmp:比较
  • strlen:统计可见长度

C语言二维数组

认识二维数组

  • 如何创建二维数组
//NO.1 如何创建二维数组,固定语法: 类型 数组名[数组长度1][数组长度2];
int array[2][2]; 
//数组长度1: 2-->行
//数组长度2: 2-->列
//总元素个数:数组长度1*数组长度2
//最大下标是: 数组名[数组长度1-1][数组长度2-1]   array[1][1]
  • 在内存上的长相

在这里插入图片描述

常规操作

  • 数组的初始化
  • 数组的遍历
//No.1 完整初始化
int array1[2][2]={1,2,3,4};
//No.2 默认初始化
int array2[2][2]={1,2};
//No.3 带{}默认初始化 ,一个{}代表一行
int array3[3][4]={{1},{2},{3}};   //第一行第一个元素:1,第二个第一个元素:2,第三行第一个元素:3
//No.4 数据完整初始化,数组长度1可以不写
int array[][3]={1,2,3,4,5,6,7,8,9}; //自动推导数组长度1是:3
  • 二维数组的基本操作
    • 行列数据的操作(excel表数据操作)
      • 行求和
      • 列求和
    • 二维数组充当地图
    • 矩阵变化
  • 二维数组操作字符串
    • 初始化
    • 遍历
    • 多个字符串排序

C语言函数

认识函数

//No.1 学会创建函数
函数返回值类型  函数名(函数参数)
{
	//函数体
    return  函数返回值;
}
//没有返回值写: void :没有
//没有参数可以不写
void  函数名()  //void 函数名(void)
{
	//没有返回值不代表没有return
    return ;	//return可以不写,没有返回值可以写
    //可以结合if语句提前结束函数
    printf("永远不会执行");	
}
//函数返回值类型: 是由return后面值的类型决定
int ReturnInt()
{
	return 1;    
}
double ReturnDouble()
{
	double d=1.11;
    return d;
}
char ReturnChar()
{
	return 'A';    
}
//函数名: 和标识符其名的规则一样
//函数参数:
//形参--->创建函数时用的参数(类型修饰的)
//实参--->调用函数时用的参数(没有类型修饰)
int Max(int a,int b)		//形参 int a,int b //int a=1 ,int b=2
{
    return a>b?a:b;
}
//调用函数: 函数名(实参); 	//形参和实参类型必须一致
Max(1,2);			//1,2实参
int a=1,b=3;
Max(a,b);			//a和b叫做实参
//函数调用每一次使用的内存都是不一样。
//所有传参的方式都是用赋值的方式传参  Max(1,2)
#include <stdio.h>
//No.1 无参,无返回的函数,用来做效果,
//这种效果一般可能需要重复使用
void print() 
{
	//return;  函数遇到这个reutrn就结束
	printf("----------------------\n");
	printf("\t0.退出\n");
	printf("\t1.录入\n");
	printf("\t2.浏览\n");
	printf("----------------------\n");
}
//No.2 带返回值 带参
//用来做数据的加工
//函数处理后结果需要在调用者中使用,可以同函数返回值体现
int Max(int a, int b)    //int a=aa,int b=bb
{
	return a > b ? a : b;//2
}
//拷贝本的问题
void modifyNum(int a)   //int a=num;
{
	a = 1004;			//局部的
	printf("%d\n", a);
}
void printA() 
{
	//a = 0;
}
int main() 
{
	print();  //函数名(参数)		
	print();
	int aa = 1;
	int bb = 2;
	int result=Max(aa, bb);	//函数返回值是函数调用表达式代表值
	//Max(aa, bb) = 101;    //函数返回值是一个值,不能充当左值
	printf("Max=%d\n", result);
	printf("Max=%d\n", Max(3, 4));	//Max(3, 4)是一个int的值,所以直接打印
	printf("Max=%d\n", Max(Max(5, 6), Max(4, 3)));
	//上面一行等效下面三行
	int first = Max(5, 6);
	int second = Max(4, 3);
	printf("Max=%d\n", Max(first, second));

	//所有传参方式都是赋值方式
	int num = 100;
	modifyNum(num);
	printf("num=%d\n", num);
	//printf("a=%d\n", a);
	return 0;
}

函数传参

#include <stdio.h>
#include <string.h>
//No.1 对于函数的每一个参数自己都要能解释清除
int Max(int a, int b)	//功能:求最大值, 求谁的最大值(a和b的最大)
{
	return a > b ? a : b;
}
//No.2 传一维数组
//数字类的数组必须要传入数组长度
void printArray(int num[], int arrayNum) 
{
	for (int i = 0; i < arrayNum; i++) 
	{
		printf("%d\t", num[i]);
	}
	printf("\n");
}
//void printArray2(int* num, int arrayNum); 和上面写法等效
//字符类的不需要长度 ,字符串结束标记
//strlen
int my_strlen(char str[])   //str="ABC"
{
	int count = 0;
	while (str[count] != '\0')	//str[0]:'A'  str[1]:'B' str[2]:'C'  str[3]:'\0'
		count++;				//count=1     count=2    count=3
	return count;				//count=3
}
//int my_strlne(const char* str);   //const常属性
//No.3 函数调用的时候,形参和实参类型必须一致
//不一致会存在强制类型转换 ,如果转换不了直接崩掉
void print(int num)  //int num='A';  //int num="ILoveyou";
{
	printf("\nnum=%d\n", num);
}
//No.4 二维数组独有传参方式
//行可以不同,但是列必须相同
void printArray2D(int array[][4], int row, int cols) 
{
	for (int i = 0; i < row; i++) 
	{
		for (int j = 0; j < cols; j++) 
		{
			printf("%d\t", array[i][j]);
		}
		printf("\n");
	}
}
int main() 
{
	Max(3, 4);			//赋值的方式传参
	int array[3] = { 1,2,3 };
	//写一个在函数打印数组
	printArray(array, 3);
	int array2[5] = { 1,2,3,4,5 };
	printArray(array2, 5);
	char str[] = { "ILoveyou" };
	printf("%d\t", my_strlen("ABC"));
	printf("%d\t", my_strlen(str));
	printf("%zd\t", strlen(str));		//zd  ---unsigned int 
	print('A');
	//print("ILoveyou");
	// 一些编译器只会提醒不报错,但是应该把编译器这种提醒当做错误
	// warning C4047: “函数”:“int”与“char [9]”的间接级别不同
	// warning C4024: “print”: 形参和实参 1 的类型不同
	int array2D[3][4] = { {1},{2},{3} };   //其他的值默认初始化为0
	printArray2D(array2D, 3, 4);
	//所有的数组为参数,都是传入数组名

	return 0;
}

函数调用

  • 普通调用: 函数名(实参) ,保持形参和实参的类型和数目一致即可
  • 嵌套调用
    • 成为一个函数参数
    • 自己调用自身(递归调用)
      • 退出性条件
      • 推导公式

标准库中一些函数

  • printf和scanf返回值
  • 随机函数

C语言指针基础篇

什么是指针

指针就是一个地址,如何获取地址?用的是&:取地址符

  • 指针就是一个整数
  • 获取指针:&

指针变量

存放地址的,也就是存放一个特定的整数(这个的整数可以表示地址)

  • 如何产生一个指针变量

​ 类型* 变量名;

​ 类名 *变量名;

  • 指针变量的两个重要概念
    • 指针的类型: 去掉变量名
    • 指针所指向的类型:去掉变量名和*号

​ 用指针的时候需要保持上述两个类型的一致

int* p;
//类型: int*
//所指向的类型: int  --->本质就是指针所操作数据类型
int(*pp)[3];  //--->指针
//int(*)[3];
//int[3]--->pp操作的就是一个数组
int* pArray[3];//--->数组
//上面两个本节课不需要考虑如何使用,下节课在做研究		
  • 不同类型的指针变量
    • 所有类型指针占用的字节数都是相同 32位系统下都是4个字节
    • 特殊的指针类型: void*
    • 专门用来初始化指针变量的东西:NULL
      • 防止悬浮指针
      • 防止野指针
    • 指针变量,获取当前地址中值: *指针变量

指针的运算

  • *指针变量: 指针变量所指向的内存存储的值
  • p+n或者p-n操作:实质是内存字节偏移,和指向内存存储的数据类型有关
    • p+sizeof(指针所指向的类型)*n
    • 对于一个指针变量来说,不会单独的去偏移,一般要指向一段内存做偏移(数组)
    • 对于不同类型的指针之间是没有什么算术运算
    • p++ 和p-- 也算p+n

内存四区

在这里插入图片描述

#include <stdio.h>

void print() 
{
	static int num = 1;		//这个代码运行的时候只执行一次
	//不做初始化默认为0
	num++;
	printf("%d\n", num);
}

char* returnPoint() 
{
	//返回局部变量地址,不允许的
	//函数调用完,内存会被系统回收(清除所有数据)
	char array[10] = "ILoveyou";
	//%s 打印方式,从首地址开始,打印到\0
	char* p = &array[0];
	//把数据存到堆区,返回堆区这段内存的首地址
	return p;
}
int main() 
{
	print();		//num=2;
	print();		//num=3;
	//num = 3;		//错误静态变量有作用
	int* p = NULL;	//0存放在常量区
	//*p = 1234;		//不能修改0所在的内存   写入访问权限冲突
	//printf("%d", *p);	
	//------------------------------------------------------
	char* str = "ILoveyou";  //解析: 把这段内存的首地址赋值给指针变量
	char* pchar;
	puts(str);
	pchar = "ILoveyou";
	//*pchar = 'M';		   //写入访问权限冲突。
	puts(pchar);
	//-------------------------------------------------------
	char array[10] = "ILoveyou";
	pchar = &array[0];
	*pchar = 'M';
	puts(array);

	int* result = returnPoint();
	puts(result);
	puts(result);
	puts(result);
	puts(result);
	return 0;
}

万能指针

  • 万能指针就是void* 类型的指针变量
  • 万能指针在访问数据的时候必须做强制类型类型
#include <stdio.h>
int main() 
{
	int num = 10;
	void* pVoid = &num;    //解析方式: pVoid=&num; 不是*pVoid=&num;
	printf("%d\n", *(int*)pVoid);
	double dNum = 1.11;
	pVoid = &dNum;
	printf("%.3lf\n", *(double*)pVoid);
	//万能指针使用的时候要强制转换为目标类型(就指向数据类型的指针)
	int number = 0x00410042; //高低字节:左边:高  右边:低
	printf("%d\n", number);
	void* p = &number;
	char* pp = (char*)p;
	//两位十六进制位是不是一个字节
	//一个十六进制位是4个二进制位
	//8个二进制位是不是一个字节  8个二进制位是不是2个十进制数
	//42 00 41 00
	printf("%c\n", *pp);	//42 -->B	//0000005B9FDBF5E4  低地址
	char* pc = (char*)p;		
	printf("%c\n", *(pc + 2));//41-->A	//000005B9FDBF5E6   高地址
	// -->小端模式  高字节存放到内存地址高的地址上
	// -->大端模式  高字节存放到内存地址低的地址上
	//十进制:1235
	//1高位 5低位
	printf("%p\n", pp);		//0000005B9FDBF5E4
	printf("%p\n", pc + 2);	//0000005B9FDBF5E6
	//万能指针应用: 统一接口(统一函数传参)--->目前不需要大家掌握
	//malloc(void* p,int arrayNum); 会使用这样的函数即可,不需要自己会写
	//数据类型在讲数据类型讲过
	return 0;
}

C语言指针与数组

指针操作数组

  • 指针操作一维数组
//int array[3]
//数组名: 代表了这段内存首地址  --->指针
//&array[0]
//正常操作: 直接存储数组的首地址
//直接把指针当作数组名就可以了
//非正常操作:指向其他元素开始操作数组

#include <stdio.h>
int main() 
{
	int array[3] = { 1,2,3 };
	printf("%p\n", array);
	printf("%p\n", &array[0]);
	int* p = array;
	for (int i = 0; i < 3; i++) 
	{
		//printf("%d\t", p[i]);  //--->这种方式使用不会错误
		printf("%d\t", *(p + i));
		//*(p+i) 等效p[i];
	}
	printf("\n");
	//array = array + 3;  不能修改
	//array-->&array[0]
	//array+3-->&array[3];
	for (p = array; p < array + 3; p++) 
	{
		printf("%d\t", *p);
		//*p等效p[0]  取当前地址对应内存中值
	}
	printf("\n");

	int test[3] = { 1,2,3 };
	int* pT = &test[2];
	printf("%d\n", pT[0]);   //指针操作数组,用数组用法,[0]下表不一定是第0个元素
	printf("%d\n", pT[-1]);
	printf("%d\n", pT[1]);	 //未知量  -858993460
	return 0;
}
//一级指针操作字符串
	char str[] = "ILoveyou";
	char* pstr = str + 3;
	puts(pstr);
	//[]操作等效取*运算
	putchar(pstr[0]);   //pstr[0] 等效*pstr操作,等效取当前地址下面值
	//*pstr 等效*(str+3) 等效 str[3];
	char* lastchar = str + 8;
	putchar(lastchar[0]);
	pstr = str;
	printf("\n");
	puts(pstr);
  • 指针操作二维数组
//二维数组基础知识
//数组名:指针数组的指针
//行地址:数组名[i];
//一级指针操作二维数组 (二维数组在内存连续的)
//数组指针操作二维数组
#include <stdio.h>
//void printArray2D(int p[][3], int cols, int row) 
void printArray2D(int(*p)[3], int cols, int row) 
{
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", p[i][j]);   //自己写代码这样就行,怎么方便怎么来
		}
		printf("\n");
	}
}

int main()
{
	int array[2][3] = { 1,2,3,4,5,6 };
	int* p = &array[0][0];
	//warning C4047: “=”:“int *”与“int (*)[3]”的间接级别不同
	//p = array;
	printf("%p\n", array);
	printf("%p\n", &array[0][0]);
	printf("%p\n", array[0]);

	printf("%p\n", array + 1);   //12/4=3
	printf("%p\n", p + 1);		 //4
	printf("%p\n", array[0] + 1);
	//char* pC = &array[0][0];
	//int* pI = &array[0][0];
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", *(p + j + i * 3));
			//j+i*3 把坐标转为第几个元素
			//p+n 等下偏移
		}
		printf("\n");
	}

	int(*pArray)[3] = NULL;    //括号必须要有,列数和表示的数组的列数必须相同
	//先算() ,他是一个子指针, 数组指针  指向数组的指针
	//联想整形指针,指向整数
	pArray = array;
	//直接当作数组名去名
	printf("-------------------------------\n");
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", pArray[i][j]);   //自己写代码这样就行,怎么方便怎么来
		}
		printf("\n");
	}
	printf("-------------------------------\n");
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", *(*(pArray + i) + j));
			//一维数组:p[i] 等效 *(p+i)
			//printf("%d\t", *(pArray[i] + j));
			//取* 等效数组下表运算 *(pArray+i)  (pArray+i)[0]
			//printf("%d\t", *((pArray + i)[0] + j));
			//printf("%d\t", ((pArray + i)[0] + j)[0]);
			//printf("%d\t", (pArray[i] + j)[0]);
			//看到会读就OK
		}
		printf("\n");
	}
	printf("-------------------------------\n");
	printArray2D(array, 2, 3);

	//存储多个字符串--->指针数组 多个指针变量
	//整形数组: 多个整形变量
	char* pstr[3] = { "Iloveyou","IMiss","IiIIII" };  //每一行上面字符串可以不相同,并且不浪费
	//int array[3]  array[0] array[1] array[2]
	//pstr[0] pstr[1] pstr[2]; 分别存储的是每个字符串首地址
	for (int i = 0; i < 3; i++) 
	{
		puts(pstr[i]);
	}
	//字符串二维数组也可以存储多个字符串? 列数相同 会导致浪费,多余内存
	return 0;
}

二级指针

//int** p=NULL;
//int*** pp=NULL;
#include <stdio.h>
int main() 
{
	int a = 1;
	int* p = &a;		//一级指针变量存储的普通变量的地址
	int** pp=&p;		//二级指针变量存储一级指针变量地址
	//*地址 --->得到当前地址对应内存中的值
	printf("%d\t%d\n", *p, p[0]);
	printf("%d\t%d\n", **pp, pp[0][0]);
	int array[2][3] = { 1,2,3,4,5,6 };
	//一级指针算偏移
	//数组指针做遍历
	// warning C4047: “=”:“int **”与“int (*)[3]”的间接级别不同
	// 所有这种提醒麻烦大家当作错误处理 C++当作是错误的
	//pp = array;
	//for (int i = 0; i < 2; i++) 
	//{
	//	for (int j = 0; j < 3; j++) 
	//	{
	//		printf("%d\n", pp[i][j]);
	//	}
	//	printf("\n");
	//}
	return 0;
}

动态内存申请

  • 动态内存申请的函数
//No.1 断言
#include <assert.h>
assert(p);		//p等于 触发断点,中断程序 断言

#include <malloc.h>
//#include <stdlib.h>
//No.2 malloc  动态内存,不做初始化
void*  malloc(size_t _Size);  //size:申请内存的总字节数
//No.3 calloc  动态内存申请,会初始化为0
void*  calloc( size_t _Count,size_t _Size); //有几个数据:count,每个数据占用的字节数:size
//No.4 realloc  内存重新申请,保留原数据
void*  realloc(void*  _Block,size_t _Size); //原指针,重新申请的内存大小
//No.5 释放内存函数
void  free(void* _Block);
//__cdecl 调用准则,限定参数传参顺序(入栈顺序)--->了解一下
//_In_ 参数类型修饰 传入
//_out_ 传出参数
//size_t  unsigned int
//void* 使用前必须强制类型转换
  • 一维数组
  • 二维数组

C语言函数与指针

指针函数

指针函数就是用到指针函数,它是一个函数

  • 指针当作函数参数(如何修改实参)

  • 指针当作函数返回值

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void modify(int count)  //int count=1
{
	count = 111;
}
void modifyCount(int* p) //int *p=&count
{
	*p = 999;
}
//址传递
//值传递
/*
	子函数想要修改普通变量实参,需要传入普通变量地址:一级指针,在修改的是普通变量
	子函数想要修改指针变量实参,需要传入指针变量地址: 二级指针,在修改的是一级指针
*/
int g_num = 1992;
void modifyPoint(int** p) 
{
	//修改了一级指针变量值
	*p = &g_num;
}
//---->无头链表
//当做函数返回值--->不能返回局部变量地址
//warning C4172: 返回局部变量或临时变量的地址: num
int* returnPoint() 
{
	int num = 100;		//局部变量, 100会被回收
	return &num;
}

int* returnPoint2(int* p)
{
	return p;
}
//也是被允许,堆区内存不会自动回收
int* returnMalloc() 
{
	int* p = (int*)malloc(sizeof(int));
	assert(p);
	*p = 11334;
	return p;
}
int main() 
{
	int count = 1;
	modify(count);
	printf("%d\n", count);
	modifyCount(&count);
	printf("%d\n", count);	//999
	int* p = &count;
	printf("%d\n", *p);
	modifyPoint(&p);
	printf("%d\n", *p);
	int* result = returnPoint();
	printf("%d\n", *result);
	printf("%d\n", *result);
	printf("%d\n", *result);
	result = returnPoint2(p);
	int* pMory = (int*)malloc(sizeof(int) * 3);
	result = returnMalloc();
	free(result);
	result = NULL;
	free(pMory);
	pMory = NULL;
	return 0;
}

函数指针

函数指针是什么

函数指针: 指向函数的指针,(函数名就是函数指针,&函数指针)

作用: 用来指向函数,调用函数

如何创建一个函数指针变量

(*指针名)替换函数名 剩下照抄,形参名可以省略,这就是函数指针

通过和函数指针调用函数

  • 直接用指针名替换函数去调用函数
  • (*指针名)替换函数名调用函数

函数指针一般是用在充当函数的参数

(专业术语叫做回调函数)

typedef与函数指针

  • typedef基本用法
  • typedef与函数指针
#include <stdio.h>


void print(int(*p)(int, int), int a, int b) 
{
	printf("%d\n", p(a, b));
}
//和上面函数一样的
typedef int(*FUNC)(int, int);
void print2(FUNC p, int a, int b)   //FUNC p	 int(*p)(int, int);
{
	printf("%d\n", p(a, b));
}
//typedef 给结构体起别名 后面讲
int main()
{
	//No.1 给基本数据类型起别名
	typedef int 整数;
	int a1 = 1;
	整数 a2 = 1;			//int  a2 = 1
	//typedef unsigned int  size_t;
	size_t a3 = 1;
	//No.2 数组起别名
	typedef int ARRAY[3];
	ARRAY arr = { 1,2,3 };					// int arr[3];   int arr[3];
	for (int i = 0; i < 3; i++) 
	{
		printf("%d\n", arr[i]);
	}
	ARRAY array[3] = { 1,2,3,4,5,6,7,8,9 };	// int array[3][3];
	typedef int A2D[3][3];
	A2D array2d;							// int array2d[3][3];
	return 0;
}

万能指针充当函数指针

  • 万能指针: 使用必须做强制类型转换
  • 指针类型: 去掉变量名

C语言自定义类型

结构体

  • 什么是结构体 (不同类型的变量集合,数组: 相同类型变量集合)

在这里插入图片描述

  • 创建结构体的语法
struct 结构名
{
    //结构体成员: 变量
}//结构体一般描述是一系列事物的抽象
//抽象的是共同的属性(特征-->数据描述)
  • 如何结构体:充当记录复杂数据功能
    • 定义一个结构体变量
      • 普通结构体变量: 类型 变量名;
      • 指针变量: 类型* 变量名;
      • 数组: 类型 数组名[数组长度];
    • 如何访问结构体中数据
      • 结构体中数据只能通过结构体变量(当然指针算是特殊结构体变量,数组特殊结构体变量)
      • 访问方式只有两种
        • 变量.成员
        • 指针->成员 ->:指针指向运算符
  • 结构体变量的初始化
    • 定义的初始化,必须类型上要和成员顺序一致
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <malloc.h>
struct student 
{
	char name[20];
	int age;
	int math;
	int score;
};

int main() 
{



	//No.1 定义变量
	struct student  memory = { "Iloveyou",18,59,59};
	struct student joker;
	//先创建后初始化,成员变量以前是怎么用,现在就怎么用
	//前缀的含义是主体,表示整个成员是谁。
	//joker.name = "joker";  //字符数组不能直接复制
	strcpy_s(joker.name, 20, "joker");
	joker.age = 18;
	joker.math = 99;
	joker.score = 99;
	//No.2 用户输入结构体变量
	struct student sun;
	printf("请输入学生信息:");
	scanf_s("%s%d%d%d", sun.name, 20, &sun.age, &sun.math, &sun.score);
	printf("%s\t%d\t%d\t%d\n", sun.name, sun.age, sun.math, sun.score);
	//No.3 结构体数组--->管理表格数据
	printf("请输入3个学生信息:");
	struct student array[3];
	for (int i = 0; i < 3; i++) 
	{
		scanf_s("%s%d%d%d", array[i].name, 20, &array[i].age, &array[i].math, &array[i].score);
	}
	printf("姓名\t年龄\tmath\tscore\n");
	for (int i = 0; i < 3; i++) 
	{
		printf("%s\t%d\t%d\t%d\n", array[i].name, array[i].age, array[i].math, array[i].score);
	}
	printf("姓名\t年龄\tmath\tscore\n");
	for (int i = 0; i < 3; i++)
	{
		printf("%s\t%d\t%d\t%d\n", (array + i)->name, (array + i)->age, (array + i)->math, (array + i)->score);
	}
	//No.4 指针
	struct student* p = NULL;
	//p->age = 12;   //错误,空里面没有这个age
	//结构体指针变成结构体变量
	//4.1 指向普通变量
	p = &memory;
	printf("%s\t%d\t%d\t%d\n", p->name, p->age, p->math, p->score);
	printf("%s\t%d\t%d\t%d\n", (*p).name, (*p).age, (*p).math, (*p).score);
	//->: 怎么打出来 先打减号 在打大于号
	//4.2 动态内存申请
	struct student* pM = (struct student*)malloc(sizeof(struct student));
	assert(pM);
	scanf_s("%s%d%d%d", pM->name, 20, &pM->age, &pM->math, &(*pM).score);
	printf("%s\t%d\t%d\t%d\n", pM->name, pM->age, pM->math, pM->score);
	free(pM);
	pM = NULL;
	return 0;
}

联合体

#include <stdio.h>
#include <stdlib.h>
//联合体:共用体
union  MM
{
	char name[20];
	int age;
};
//基本不用,但是网络库中

int main() 
{
	//所有数据成员用的是同一段内存,最长的那个变量的内存
	//所以定义的时候不能直接初始化两个数据
	//任何时候共用体只有一个数据有效
	union MM mm = { "A" };
	//union MM mm2 = { "A" ,68};  //错误的写法
	printf("%d\n", mm.age);
	//union MM mm2 = { 68 };   //00 00 00 44    //44 00 00 00
	//printf("%s", mm2.name);

	return 0;
}

枚举类型

#include <stdio.h>
#include <stdlib.h>
//enum 枚举类型名{符号1,符号2,...};
enum COLOR{Red,Green,Blue,Yellow};
//枚举类型就是一个常量,就是整数
//默认初始化: 从0开始一次初始化
//增加代码可读性,防止幻数-->大量的常量
//手动初始化
enum  Eement {Wall=1,Road=0,Box=4,End=3,People=5};
//部分初始化,部分默认
//不写的是写了+1
enum NUM {a=1,b,c=4,d};
//b=a+1:2
//d=c+1:5
void print(int num) 
{
	printf("%d\n", num);
}
//C语言把枚举理解int
void printEnum(enum COLOR cnum) 
{
	printf("%d\n", cnum);
}
//enum class
int main() 
{

	printf("red:%d\n", Red);
	printf("Green:%d\n", Green);
	printf("Blue:%d\n", Blue);
	printf("Yellow:%d\n", Yellow);
	printf("%d\t%d\t%d\t%d\n", a, b, c, d);
	print(Red);
	printEnum(a);
	printEnum(1);
	return 0;
}

位段

#include <stdio.h>
//特殊的结构体:内存精确到二进制位
//作用:节省内存
struct Data 
{
	unsigned int a : 3;  //_ _ _   000  111:7
	unsigned int b : 1;  //0 1
	unsigned int c : 4;  //0000 1111:15
};
//寄存器的描述
//标志位
//12*4=48
//4个字节

int main()
{
	struct Data data = { 8,1,18 };
	printf("%d,%d,%d", data.a, data.b, data.c);
	return 0;
}

内存对齐

增加无用字节去补齐内存

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//1.建议: 小的内存写上面,大的写下面
//字符可以拆分,算最小的
struct A 
{
	char name[5];		//整数和字符可以合一起
	int a;
	double b;
};
struct B 
{
	char name[20];
	//8
	//8
	//4补4
	double b;  //8
	int a;     //4补4
};
//2.内存对齐并不重要,一般只会在考题中,写程序算占用内存一个sizeof搞定
//3.为什么需要内存对齐-->地址总线--->有兴趣可以自己了解
struct C 
{
	int* p;				//x64 :8 -->64位
	char str[10];		//x86--->32位
	int a;
	x86--->32位
	//4
	//4
	//4
	//2 补齐2
	//4
};
//骚操作
struct Data 
{
	int a;
	char str[3];
	int b;
};
void test()
{
	struct Data pData = { 1,"A",4 };
	void* p = &pData;
	int* pInt = p;
	printf("%d\n", *pInt);		//pData.a;
	pInt++;
	printf("%s\n", (char*)pInt);//pData.str;
	pInt++;	
	printf("%d\n", *pInt);		//pData.b;

	struct Data* pp = &pData;
	printf("%d\n", pp->a);
	printf("%s\n", pp->str);
	printf("%d\n", pp->b);
}


int main() 
{
	test();
	printf("\nA:%zd\n", sizeof(struct A));
	printf("B:%zd\n", sizeof(struct B));
	printf("B:%zd\n", sizeof(struct C));
	struct B* p = (struct B*)malloc(sizeof(struct B));
	assert(p);
	free(p);
	p = NULL;

	return 0;
}

C语言链表

什么是链表

链表是一种物理存储单元上的非连续,非顺序的存储结构,数据元素的逻辑顺序通过链表中指针连接的。

通俗一点: 链表就是多个结构体变量通过结构体指针连接起来

链表节点组成

  • 数据域
  • 指针域
struct student
{
  char name[20];
  int age;
  int num;
};
struct Node
{
    //struct student data
    int data;		     //数据域,应用链表时候,这个数据是一个结构体
    struct Node* next;   //指针域,
};

简单链表

在这里插入图片描述

#include <stdio.h>
#include <assert.h>
struct Node 
{
	int data;
	struct Node* next;
};
int main() 
{
	int a = 1;
	int* pa = &a;
	//正常操作
	struct Node data = { 1,NULL };		//结构体变量
	struct Node* p = &data;
	printf("%d\n", p->data);			//通过结构体指针访问数据
	printf("%p\n", p->next);

	//链表就是多个结构体变量
	struct Node node1 = { 1,NULL };		//节点1
	struct Node node2 = { 2,NULL };		//节点2
	struct Node node3 = { 3,NULL };		//节点3

	node1.next = &node2;
	node2.next = &node3;

	//链表的打印
	struct Node* pmove = &node1;
	printf("第一个节点数据:%d\n", pmove->data);
	printf("第二个节点数据:%d\n", pmove->next->data);
	printf("第三个节点数据:%d\n", pmove->next->next->data);
	//上面不太方便,用循环来做
	while (pmove != NULL) 
	{
		printf("%d\t", pmove->data);
		pmove = pmove->next;     //pmove=pmove->next
	}
	//强调比较重要的概念
	//指针怎么变成变量
	//1.指向变量,*指针等效变量
	int a = 1;
	int* pa = &a;
	//2.动态内存是申请一个变量内存
	int* pM = (int*)malloc(sizeof(int));
	assert(pM);
	*pM = 134;
	return 0;
}

链表的分类

根据第一个节点是否存放了有效数据

  • 有头链表:表头永远都不会变量
  • 无头链表: 表头会改变

在这里插入图片描述

(无头链表,双向链表,循环双向链表,有序链表)

链表实现代码封装

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//链表基础数据结构
//1.封装单一个体
struct Node 
{
	int data;
	struct Node* next;
};
//2.创建表头-->创建一个结构体变量
//有头链表,第一个节点不存放数据
struct Node* createHead() 
{
	struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
	assert(headNode);
	//(*headNode).data = 0;  存储当前链表的节点数
	headNode->next = NULL;
	return headNode;
}
//3.创建节点,为插入做准备
struct Node* createNode(int data) 
{
	struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
	assert(newNode);
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}
void insertByHead(struct Node* headNode,int data) 
{
	struct Node* newNode = createNode(data);		//调用创建节点的函数,把用户的数据变成一个节点
	newNode->next = headNode->next;
	headNode->next = newNode;
}
void insertByTail(struct Node* headNode, int data) 
{
	if (headNode == NULL)
	{
		printf("链表不存在的!\n");
		return;
	}
	struct Node* newNode = createNode(data);
	struct Node* tailNode = headNode;
	while (tailNode->next != NULL) 
	{
		tailNode = tailNode->next;
	}
	tailNode->next = newNode;
}
//很多种方式
//1.以数据作为参照
void insertByAppoin(struct Node* headNode, int posData, int data) 
{
	struct Node* preNode = headNode;
	struct Node* curNode = headNode->next;
	//条件不能交换
	//curNode等于NULL,NULL->data
	while (curNode != NULL && curNode->data != posData) 
	{
		//preNode = preNode->next;
		//curNode = curNode->next;
		//等效下面写法
		preNode = curNode;
		curNode = preNode->next;
	}
	//分析结果
	if (curNode == NULL) 
	{
		printf("没有找到指定节点,无法插入!\n");
	}
	else 
	{
		struct Node* newNode = createNode(data);
		preNode->next = newNode;
		newNode->next = curNode;
	}
}
//2.以第几个节点作为参照
void insertByIndex(struct Node* headNode, int index, int data) 
{
	if (index <= 0)
	{
		printf("序号有问题,不能插入!");
		return;
	}
	int count = 1;							//不存在第0个元素
	struct Node* preNode = headNode;		//头结点
	struct Node* curNode = headNode->next;	//头结点下一个
	while (curNode!=NULL&&count < index) 
	{
		preNode = curNode;
		curNode = preNode->next;
		count++;
	}
	if (curNode == NULL) 
	{
		printf("序号有问题,无法插入!\n");
	}
	else 
	{
		struct Node* newNode = createNode(data);
		preNode->next = newNode;
		newNode->next = curNode;
	}
}
//删除
void deleteByHead(struct Node* headNode) 
{
	//.c文件没有省略struct的写法,.cpp才有省略struct写法
	struct Node* nextNode = headNode->next;
	if (headNode==NULL||nextNode == NULL) 
	{
		printf("链表为空,无法删除!\n");
		return;
	}
	else 
	{
		headNode->next = nextNode->next;
		free(nextNode);
		nextNode = NULL;
	}

}
void deleteByTail(struct Node* headNode) 
{
	
	struct Node* preNode = headNode;
	struct Node* tailNode = headNode->next;
	if (headNode == NULL || tailNode == NULL)
	{
		printf("链表为空,无法删除!\n");
		return;
	}
	while (tailNode->next != NULL) 
	{
		preNode = tailNode;
		tailNode = preNode->next;
	}
	preNode->next = NULL;
	free(tailNode);
	tailNode = NULL;
}
void deleteByAppoin(struct Node* headNode,int posData) 
{
	struct Node* preNode = headNode;
	struct Node* curNode = headNode->next;
	if (headNode == NULL || curNode == NULL)
	{
		printf("链表为空,无法删除!\n");
		return;
	}
	while (curNode != NULL && curNode->data != posData) 
	{
		preNode = curNode;
		curNode = preNode->next;
	}
	if (curNode == NULL) 
	{
		printf("未找到指定位置无法删除!\n");
	}
	else 
	{
		preNode->next = curNode->next;
		free(curNode);
		curNode = NULL;
	}
}
//删掉所有的相同的
struct Node* searchNode(struct Node* headNode,int posData) 
{
	struct Node* pmove = headNode->next;
	while (pmove != NULL && pmove->data != posData) 
	{
		pmove = pmove->next;
	}
	return pmove;
}
void deleteAll(struct Node* headNode, int posData) 
{
	while (searchNode(headNode, posData) != NULL) 
	{
		deleteByAppoin(headNode, posData);
	}
}
//销毁链表
void destoryList(struct Node** headNode) 
{
	while ((*headNode)->next != NULL)
	{
		deleteByHead(*headNode);
	}
	free(*headNode);
	*headNode = NULL;
}
//打印链表
void printList(struct Node* headNode)
{
	struct Node* pmove = headNode->next;	//因为是有头的,所以要从第二个开始打印
	while (pmove != NULL)
	{
		printf("%d\t", pmove->data);
		pmove = pmove->next;
	}
	printf("\n");
}
void print(int data) 
{
	printf("%d\n", data);
}
int main() 
{
	int a = 1;
	print(a);
	print(a);
	print(a);
	struct Node* headNode = createHead();  //这就是创建的链表
	for (int i = 1; i < 3; i++) 
	{
		insertByHead(headNode, i);
	}
	printList(headNode);
	insertByTail(headNode, 1);
	insertByTail(headNode, 2);
	insertByTail(headNode, 3);
	printList(headNode);
	//321123
	insertByAppoin(headNode, 1, 666);
	printList(headNode);
	insertByIndex(headNode, 1, 888);
	printList(headNode);
	deleteByHead(headNode);
	printList(headNode);
	deleteByTail(headNode);
	printList(headNode);
	deleteByAppoin(headNode, 666);
	printList(headNode);
	deleteAll(headNode, 1);
	printList(headNode);
	destoryList(&headNode);
	printf("%p\n", headNode);
	return 0;
}

链表写管理系统

写好链表后,只需要修改和数据有关的操作,一般使用链表只需要以下操作

  • 创建头和创建节点
  • 一种插入方式
  • 一种指定位置删除
  • 查找
  • 遍历
  • 链表排序:冒泡排序等都可

C语言文件操作

文件基础

文件操作: 针对磁盘是文件做一个读写操作

  • 标准输入: stdin —>键盘输入
  • 标准输出: stdout—>控制台
  • 自定义文件: FILE* fp

文本文件

二进制文件

1.文件打开

FILE* fopen(const char* fileName,const char* mode);
//filename: 文件路径
//mode: 读写方式
/************************************************************
*		w:  写的方式(write) 创建文件方式写,原文件存在就清空
*		r:  读的方式(read)  
*		a:  追加模式(append) 不清空原文件写操作,在原文件末位接着写+可创建
*		+: 代表可读可写
*		w+ r+ a+
*		b: 二进制的方式
* 		wb ab rb wb+ ab+ rb+ 
*************************************************************/
//注意点: C语言打开文件不具备创建文件夹的功能

2.文件关闭

int fclose(FILE* file)

3.文件结束标记: EOF

#include <stdio.h>
int main() 
{
	FILE* fp=NULL;
	fp = fopen("1.txt", "w");
	if (fp == NULL) 
	{
		printf("文件打开失败!\n");
		return 0;
	}
	fclose(fp);
	fp = fopen("append.txt", "a");
	if (fp == NULL)
	{
		printf("文件打开失败!\n");
		return 0;
	}
	fclose(fp);
	fp = fopen("read.txt", "r");
	printf("%d\n", EOF);
	if (fp == NULL)
	{
		printf("文件打开失败!\n");
		return 0;
	}
	return 0;
}

文件读写

注意点:所有文件操作底层都做了文件指针移动,一般不需要大家手动做文件指针移动(fp++错误)

  • 以字符和字符串的方式读写

    • 字符的方式
    //fgetc(): 读操作
    //fputc(): 写操作
    int  fgetc(FILE* _Stream);
    //返回值: 读写的字符
    //参数: 文件指针,读的是那个文件
    int  fputc(int   _Character,FILE* _Stream);
    //打印的字符是什么:_Character
    //打印到哪里: 文件指针指向的文件
    
    • 字符串的方式
    //fgets();
    //fputs(); 
    int  fputs( char const* _Buffer,FILE* _Stream);
    //_Buffer写内容
    //_Stream哪个文件
    char*  fgets(char* _Buffer,int   _MaxCount,FILE* _Stream);
    //读的内容存到那里: _Buffer
    //容器大小:_MaxCount
    //从那里开始读的
    _Stream
    
#include <stdio.h>
int main() 
{
	FILE* fp=NULL;
	fp = fopen("1.txt", "w");
	if (fp == NULL) 
	{
		printf("文件打开失败!\n");
		return 0;
	}
	fclose(fp);
	fp = fopen("append.txt", "a");
	if (fp == NULL)
	{
		printf("文件打开失败!\n");
		return 0;
	}
	fclose(fp);
	fp = fopen("read.txt", "r");
	printf("%d\n", EOF);
	if (fp == NULL)
	{
		printf("文件打开失败!\n");
		return 0;
	}
	return 0;
}
  • 格式化读写

    格式化读写(表格数据读写)不适用于文件指针移动

    int fprintf(FILE* fp,const char* mode,...);
    //fp: 打印到文件指针指向文件
    //mode: 格式控制字符
    //...: 参数名表,多个变量
    int fscanf(FILE* fp,const char* mode,...);
    //fp: 读的哪个文件
    //mode: 按什么格式读
    //...: 参数地址表(多个变量的地址)
    //注意点: 
    //1.怎么写怎么读就行了
    //2.读操作不支持精度控制
    
    //结构数据的读写
    #include <stdio.h>
    #include <assert.h>
    struct MM 
    {
    	char name[20];
    	int age;
    	int num;
    };
    void writeFile(struct MM array[], int arrayNum, const char* fileName)
    {
    	FILE* fp = fopen(fileName, "w");
    	assert(fp);
    	for (int i = 0; i < arrayNum; i++) 
    	{
    		fprintf(fp, "%s\t%d\t%d\n", array[i].name, array[i].age, array[i].num);
    	}
    	fclose(fp);
    }
    void readFile(const char* fileName) 
    {
    	FILE* fp = fopen(fileName, "r");
    	assert(fp);
    	struct MM temp[3];
    	int i = 0;
    	while (fscanf(fp, "%s\t%d\t%d\n", temp[i].name, &temp[i].age, &temp[i].num) != EOF) 
    	{
    		fprintf(stdout, "%s\t%d\t%d\n", temp[i].name, temp[i].age, temp[i].num);
    		//printf("%s\t%d\t%d\n", temp[i].name, temp[i].age, temp[i].num);
    		i++;
    	}
    	fclose(fp);
    }
    int main() 
    {
    	struct MM mm[3] =
    	{
    		{"iron",18,1001},
    		{"Close",16,1002},
    		{"8K龙",19,1003}
    	};
    	writeFile(mm, 3, "fprintf.txt");
    	//readFile("fprintf.txt");
    	FILE* fp = fopen("fprintf.txt", "r");
    	assert(fp);
    	struct MM temp[3];
    	int i = 0;
    	while (fscanf(fp, "%s\t%d\t%d\n", temp[i].name, &temp[i].age, &temp[i].num) != EOF)
    	{
    		fprintf(stdout, "%s\t%d\t%d\n", temp[i].name, temp[i].age, temp[i].num);
    		//printf("%s\t%d\t%d\n", temp[i].name, temp[i].age, temp[i].num);
    		i++;
    	}
    	fclose(fp);
    	//int a=1;
    	//fscanf(stdin,"%d",&a);	//等效scanf函数
    
    	return 0;
    }
    
  • 二进制的方式读写

    size_t  fread( void*  buff,size_t size,size_t count,FILE*  _Stream);
    //读的内容存到那里: buff
    //每一次读多少字节: size
    //总共读几次: count
    //读的哪个文件:_Stream
    //注意点: 堆区内存,只能读到char* 内存中
    size_t  fwrite( void*  buff,size_t size,size_t count,FILE*  _Stream);
    //写的内容: buff
    //每一次写多少字节: size
    //总共写多少次: count
    //写的哪个文件:_Stream
    

一般用来读结构数据,注意点: 如果写到文件中是乱码大家不要介意,能成功读出来即可

文件指针

void rewind(FILE* file);		//把文件指针移动当前文件开头
int  fseek(FILE* _Stream,long  _Offset, int   _Origin);  //文件定位
//文件指针:_Stream
//相对位置偏移量:_Offset  整数:右边  负数: 左边
//参照点:_Origin
//SEEK_CUR   当位置
//SEEK_END   结束位置
//SEEK_SET   开始位置
int ftell(FILE* file);			//文件指针移动
int feof(FILE* file);			//文件是否到达文件末位

文件重定向

FILE*  freopen(char const* _FileName,char const* _Mode,FILE*  _Stream);

文件分割和合并

C语言预处理与宏定义

预处理和宏定义不是语法之类的东西,不算做语句,是没有;

宏替换

  • 单纯替换
  • 宏函数
#define MAX 100	  //用一个名字替换一个数字  
#define 整形 int   //不用的东西

条件编译

//1.条件编译 是在编译期完成
#if 表达式
//....     //成立执行这部分
#endif

#if 1
//...
#else
//....
#endif

#if 1
//...
#elif 2
//..
#elif 3
.....
#else
//....
#endif
//多字节处理
//条件编译做兼容性处理
#ifdef UNICODE
#undef UNICODE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <graphics.h>


#define NUM 10
#define MAX 100			//不这样写代码,宏统一写在头文件下面
void test() 
{
	//可以提前结束宏的作用域
	//可以坚持这个宏是否存在
#ifdef MAX
	printf("%d\n", MAX);
#endif
#ifndef LL			//如果没有定义这个宏
#define LL 100		//就自己定义
#endif
	printf("%d\n", LL);
	//取消宏的作用
#undef LL						//LL被取消
	//错误代码
	//printf("%d\n", LL);		//LL被销毁了
}
int main() 
{
#if 1
	printf("1....\n");
#endif
	int a = -3;
#if a+4>3				//1>3
	printf("a<1\n");
#endif
	//一般当做条件编译的条件是宏

#if NUM>1
	printf("num\n");
#endif
	printf("%d\n", MAX);

	outtextxy(10, 10, "dsad");

	return 0;
}

其他预处理命令

//No.1 #   直接转字符串
//No.2 ## 连接符 产生一个新名字
//No.3 #pragma
		//comment 加载静态库
		//warning: 忽略提醒
	    //pack:对齐方式
		//once
//No.4 预留宏

C语言文件写法

  • 设计多文件的时候,通常情况是一个.h 对应.c

    • 一个.h和.c 实现一个大模块
    • .h: 写的声明
    • .c: 写的实现
  • 对于自己写的头文件包含,通常在.c文件中完成

  • 多文件容易出现重定义和未定义问题

    • #pragma once

C语言命令行参数

命令行参数就是主函数参数

#include <stdio.h>
int main(int argv, char* argc[])
{
	if (argv != 3) 
	{
		printf("命令语法不正确。\n");
	}
	else 
	{
		printf("argc[0]:%s\n", argc[0]);
		printf("argc[1]:%s\n", argc[1]);
		printf("argc[2]:%s\n", argc[2]);
		//copy		1.txt    2.txt
		//argc[0]   argc[1]  argc[2]
		printf("覆盖 %s 吗? (Yes/No/All):", argc[2]);
		int userKey = getchar();
		if (userKey == 'y' || userKey == 'Y') 
		{
			FILE* f1 = fopen(argc[1], "r");
			if (f1 == NULL) 
			{
				printf("系统找不到指定的文件。\n");
				return;
			}
			FILE* f2 = fopen(argc[2], "w");
			int data = fgetc(f1);
			while (data != EOF) 
			{
				fputc(data, f2);
				data = fgetc(f1);
			}
			fclose(f1);
			fclose(f2);
			printf("已复制         1 个文件。\n");
		}
		else 
		{
			printf("已复制         0 个文件。\n");
		}
	}
	return 0;
}

C语言其他头文件

所有头文件

http://www.cplusplus.com/

在这里插入图片描述

  • aasert.h: 做断言处理
  • ctype.h: 数据类型判断的一些头文件

在这里插入图片描述

  • errno.h:描述错误代码

  • fenv.h: 浮点数据的一些设置

  • float.h:浮点数的一些特征

  • inttypes.h: 宽整数的描述

  • iso646.h: 特殊英语单词替换运算符

    #define and &&
    #define and_eq &=
    #define bitand &
    #define bitor |
    #define compl ~
    #define not !
    #define not_eq !=
    #define or ||
    #define or_eq |=
    #define xor ^
    #define xor_eq ^=
    
  • limits.h: 描述数据范围使用

#define MB_LEN_MAX    5
#define SHRT_MIN    (-32768)
#define SHRT_MAX      32767
#define USHRT_MAX     0xffff
#define INT_MIN     (-2147483647 - 1)
#define INT_MAX       2147483647
#define UINT_MAX      0xffffffff
#define LONG_MIN    (-2147483647L - 1)
#define LONG_MAX      2147483647L
#define ULONG_MAX     0xffffffffUL
#define LLONG_MAX     9223372036854775807i64
#define LLONG_MIN   (-9223372036854775807i64 - 1)
#define ULLONG_MAX    0xffffffffffffffffui64

#define _I8_MIN     (-127i8 - 1)
#define _I8_MAX       127i8
#define _UI8_MAX      0xffui8

#define _I16_MIN    (-32767i16 - 1)
#define _I16_MAX      32767i16
#define _UI16_MAX     0xffffui16

#define _I32_MIN    (-2147483647i32 - 1)
#define _I32_MAX      2147483647i32
#define _UI32_MAX     0xffffffffui32

#define _I64_MIN    (-9223372036854775807i64 - 1)
#define _I64_MAX      9223372036854775807i64
#define _UI64_MAX     0xffffffffffffffffui64
  • locale.h:环境设置
  • math.h: 数学函数
double  sqrt(_In_ double _X)					//开平方根
double  pow(_In_ double _X, _In_ double _Y)		//2的3次方
int     abs(_In_ int _X)						//求绝对值
  • setjmp.h: 跨函数跳转的语句 ---->不建议用
  • signal.h: 信号量: 做进程的,目前不需要掌握
  • stdarg.h: 可变参函数: printf函数
  • stdbool.h: bool类型
#define bool  _Bool
#define false 0
#define true  1
  • stddef.h: 定义一个宏,一般表示类型
#ifdef _WIN64
    typedef unsigned __int64 size_t;
    typedef __int64          ptrdiff_t;
    typedef __int64          intptr_t;
#else
    typedef unsigned int     size_t;
    typedef int              ptrdiff_t;
    typedef int              intptr_t;
#endif
  • stdint.h: int类型的数据描述
  • stdio.h: 标准的输入输出头文件
int  sprintf_s( char* const B, size_t   const BCount,char const* const _Format,...)
int  sscanf_s(char const* const B,char const* const F, ...)
  • tgmath.h : 数学函数宏
  • time.h: 时间处理函数
  • uchar.h: 无符号char类型的一些描述
  • wchar.h:宽字节版本的字符串处理函数
  • wctype.h: 宽字节版本的数据类型

时间头文件

  • 使用的时候加个:#include <time.h>
  • struct tm类型:
struct tm
{
    int tm_sec;   // seconds after the minute - [0, 60] including leap second
    int tm_min;   // minutes after the hour - [0, 59]
    int tm_hour;  // hours since midnight - [0, 23]
    int tm_mday;  // day of the month - [1, 31]
    int tm_mon;   // months since January - [0, 11]         //月份也是从0开始
    int tm_year;  // years since 1900						//从1900开始
    int tm_wday;  // days since Sunday - [0, 6]				//星期几:从0开始
    int tm_yday;  // days since January 1 - [0, 365]
    int tm_isdst; // daylight savings time flag
};
  • time函数可以获取时间戳
static  time_t  time(time_t* const _Time)    
  • ctime: 把时间戳转换为字符串直接打印出来
 static  char*  ctime(const* const _Time)

可变参函数

  • 可变参函数实现前加头文件: stdarg.h
  • 基础知识
//宏函数
va_list:  保存所有参数
va_start: 参数的第一个位置
va_end:  参数的最后一个位置
va_arg:	 参数
...	:   参数包:无参或者多个参数,参数的个数不定
int __CRTDECL printf(char const* const _Format,...)
  • 简单案例: 求不定参数的数字的和
#include <stdio.h>
#include <stdarg.h>
//count: 有几个参数
//data: 第一个参数
//... 参数包
int Sum(int count, int data, ...) 
{
	int i, sum = data;
	va_list ap;    //保存所有参数
	va_start(ap, data);	//第一个参数的位置是data
	for (int i = 1; i < count; i++) 
	{
		//va_arg
		sum += va_arg(ap, int);
	}
	va_end(ap);
	return sum;
}
void testSum() 
{
	printf("%d\n", Sum(3, 1, 2, 3));
	printf("%d\n", Sum(5, 1, 2, 3,4,5));
	printf("%d\n", Sum(6, 1, 2, 3,4,5,6));
}
//简单实现printf函数
void myprint(const char* str, ...) 
{
	va_list ap;
	int iNum;
	double dNum;
	va_start(ap, str);
	while (*str)
	{
		if (*str == '%') 
		{
			str++;
			switch (*str) 
			{
			case 'd':
				iNum = va_arg(ap, int);
				printf("%d", iNum);
				break;
			case 'f':
				dNum = va_arg(ap, double);
				printf("%f", dNum);
				break;
			}
		}
		else 
		{
			printf("%c", *str);
		}
		str++;
	}
}
int main() 
{
	printf("dsds\n");
	testSum();
	myprint("整数:%d小数:%f\n", 12, 1.222);
	return 0;
}

_I64_MIN (-9223372036854775807i64 - 1)
#define _I64_MAX 9223372036854775807i64
#define _UI64_MAX 0xffffffffffffffffui64


+ locale.h:环境设置
+ math.h: 数学函数

```c
double  sqrt(_In_ double _X)					//开平方根
double  pow(_In_ double _X, _In_ double _Y)		//2的3次方
int     abs(_In_ int _X)						//求绝对值
  • setjmp.h: 跨函数跳转的语句 ---->不建议用
  • signal.h: 信号量: 做进程的,目前不需要掌握
  • stdarg.h: 可变参函数: printf函数
  • stdbool.h: bool类型
#define bool  _Bool
#define false 0
#define true  1
  • stddef.h: 定义一个宏,一般表示类型
#ifdef _WIN64
    typedef unsigned __int64 size_t;
    typedef __int64          ptrdiff_t;
    typedef __int64          intptr_t;
#else
    typedef unsigned int     size_t;
    typedef int              ptrdiff_t;
    typedef int              intptr_t;
#endif
  • stdint.h: int类型的数据描述
  • stdio.h: 标准的输入输出头文件
int  sprintf_s( char* const B, size_t   const BCount,char const* const _Format,...)
int  sscanf_s(char const* const B,char const* const F, ...)
  • string.h: 字符处理函数

在这里插入图片描述

  • tgmath.h : 数学函数宏
  • time.h: 时间处理函数
  • uchar.h: 无符号char类型的一些描述
  • wchar.h:宽字节版本的字符串处理函数
  • wctype.h: 宽字节版本的数据类型

时间头文件

  • 使用的时候加个:#include <time.h>
  • struct tm类型:
struct tm
{
    int tm_sec;   // seconds after the minute - [0, 60] including leap second
    int tm_min;   // minutes after the hour - [0, 59]
    int tm_hour;  // hours since midnight - [0, 23]
    int tm_mday;  // day of the month - [1, 31]
    int tm_mon;   // months since January - [0, 11]         //月份也是从0开始
    int tm_year;  // years since 1900						//从1900开始
    int tm_wday;  // days since Sunday - [0, 6]				//星期几:从0开始
    int tm_yday;  // days since January 1 - [0, 365]
    int tm_isdst; // daylight savings time flag
};
  • time函数可以获取时间戳
static  time_t  time(time_t* const _Time)    
  • ctime: 把时间戳转换为字符串直接打印出来
 static  char*  ctime(const* const _Time)

可变参函数

  • 可变参函数实现前加头文件: stdarg.h
  • 基础知识
//宏函数
va_list:  保存所有参数
va_start: 参数的第一个位置
va_end:  参数的最后一个位置
va_arg:	 参数
...	:   参数包:无参或者多个参数,参数的个数不定
int __CRTDECL printf(char const* const _Format,...)
  • 简单案例: 求不定参数的数字的和
#include <stdio.h>
#include <stdarg.h>
//count: 有几个参数
//data: 第一个参数
//... 参数包
int Sum(int count, int data, ...) 
{
	int i, sum = data;
	va_list ap;    //保存所有参数
	va_start(ap, data);	//第一个参数的位置是data
	for (int i = 1; i < count; i++) 
	{
		//va_arg
		sum += va_arg(ap, int);
	}
	va_end(ap);
	return sum;
}
void testSum() 
{
	printf("%d\n", Sum(3, 1, 2, 3));
	printf("%d\n", Sum(5, 1, 2, 3,4,5));
	printf("%d\n", Sum(6, 1, 2, 3,4,5,6));
}
//简单实现printf函数
void myprint(const char* str, ...) 
{
	va_list ap;
	int iNum;
	double dNum;
	va_start(ap, str);
	while (*str)
	{
		if (*str == '%') 
		{
			str++;
			switch (*str) 
			{
			case 'd':
				iNum = va_arg(ap, int);
				printf("%d", iNum);
				break;
			case 'f':
				dNum = va_arg(ap, double);
				printf("%f", dNum);
				break;
			}
		}
		else 
		{
			printf("%c", *str);
		}
		str++;
	}
}
int main() 
{
	printf("dsds\n");
	testSum();
	myprint("整数:%d小数:%f\n", 12, 1.222);
	return 0;
}
  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值