文章目录
前言
第一版
第一版于2022年11月5日完稿并推送。在本稿中是作者第一次学习C语言后所总结归纳下的知识点,若文章有不妥、错误之处,欢迎随时联系作者修正。
第二版
第二版于2023年3月21日完稿并推送。在该版本中,对文章基础部分和初级部分的知识点做了相应的修正和补充,以便于读者更好的阅读学习。
第三版
第三版于2023年6月6日完稿并推送。在该版本中,对C语言进行文件操作的相关内容进行了补充。
本篇笔记所用的编译器均为 VS2019
基础部分
第一章 初始C语言
1.定义
C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发。 C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。
2.编译器
Clang、GCC、MSVC……
3.写出的一个C语言程序
①步骤(以VS2019为例)
创建项目——源文件——代码——编译代码
XXXX.c 源文件
XXXX.h 头文件
②写代码
int main() //函数名
{
//函数体
retuen 0; //函数的返回类型
}
C语言代码从主函数第一行代码开始执行
在一个工程中main函数有且只有一个
例:在屏幕上打印“helloword”
#include<stdio.h> //引用头文件
int main()
{
printf("helloword\n");//“printf”_打印屏幕信息,为库函数,需要引用头文件<stdio.h>
“\n”表示换行
return 0;
}
//按ctrl+F5运行代码
第二章 数据类型
一. 数据种类
1.char 字符数据类型; 2.short 短整型; 3.int 整型; 4.long 长整型;
5.longlong 更长整型; 6.float 单精度浮点整型;
7.double 双精度浮点整型.
例:创建一个储存字符“a”的代码。
int main ()
{
char ch = ‘a’; //char 字符类型
//“ch”表示为字符提供存储空间;'a’表示存储的内容
return 0;
}
编译器中,默认小数是double类型
二. 类型的大小
%d —— 打印一个整数
%lf —— 打印浮点型
%f —— 打印浮点数
sizeof —— 操作符 —— 计算类型(或变量)所占的空间大小(单位:字节byte)
例:
sizeof(char) —— 计算字符类型的大小;
sizeof(long) —— 计算长整型的大小.
计算机中的单位
① bit - 比特位 - 用来存放一个二进制为 - ‘0’ 或 ‘1’;
② byte - 字节 —— 1 byte = 8 bit;
③ Kb ——1 Kb = 1024 byte;
④ Mb —— 1 Mb = 1024 Kb;
⑤ Gb —— 1 Gb = 1024 Mb;
⑥ Tb; ⑦ Pb ……
各种类型的大小
char —— 1 byte;short —— 2 byte;int —— 4 byte;;long —— 4 byte;
longlong —— 8 byte;float —— 4 byte;double —— 8 byte.
三. 补充说明
1.类型众多的原因:提高空间利用率。
2.用途:用数据类型创建变量。
四. 在VS2019编译器中,scanf函数报错的解决方法
1. 核心思想:在源文件首行添加#define _CRT_SECURE_NO_WARNINGS 1
2. 在代码中自动加入所述语句的方法
- 找到“newc++file.cpp”的文件;
- 将此文件拷贝至桌面;
- 打开桌面中此文件,添加所述代码并保存;
- 用修改后的文件替换文件目录中的文件。
第三章 变量&常量
一. 定义
变量:不变的量; 常量:可变的量。
例:一个人今年的年龄为20岁,在屏幕上打印下一年的年龄。
int main () { int age = 20; age = age + 1; printf("%d\n",age); return 0; }
二. 变量
1.分类
局部变量:大括号内部的变量;
全局变量:大括号外部的变量。
例:
int a = 100; //全局变量
int main ()
{
int age = 10; //局部变量
return 0;
} // a显示的是10
即当局部变量与全局变量名称冲突时,局部变量有限!
(不建议局部变量与全局变量同名)
2.使用
例:写一个代码求两个数的和
#define _CRT_SECURE_NO_WARNINGS 1 int main () { int a = 0; int b = 0; int sum = 0; sacnf("%d %d",&a,&b); //scanf_输入函数 // 需要补充#define _CRT_SECURE_NO_WARNINGS 1 sum = a + b; printf("sum = %d\n",sum); return 0; }
3.作用域&生命周期
①作用域:
局部变量的作用域就是变量所在的局部范围;
全局变量的作用域就是整个工程。
②生命周期:变量创建和销毁之间的时间段
局部变量的生命周期是进入局部范围开始,出局部范围结束。
全局变量的生命周期是程序的生命周期。
三. 常量
1.字面常量
例:
int main ()
{
3.14;
10;
‘a’;
“abcdef”;
return 0;
}
2.const修饰的常量
//const - 不被修饰的 adj.
例:
int num = 10; // 此处num为变量
const int num = 10; // 此时处num为常变量
3.#define定义的标识符量
例:
#define MAX 1000 //表示MAX的符号为定义出来的
即定义某一量(自定义)。
4.枚举常量
用于可以一 一列举出的常量
(枚举常量通常从0开始)
例:
enum Sex //enum 枚举符号
{
“男”,“女”,“保密” //枚举类型
};
第四章 字符串&转义字符&注释
一. 字符串
1.定义
用双引号括起来的一串字符(“abcdef”)。
字符串的结束标志是一个“\0”的转义字符,通常隐藏。
字符数组是一组相同类型的元素
例:
char arr1[ ] = “abc” ⇒ “abc\0”
char arr2[ ] = {‘a’,‘b’,‘c’} ⇒ “a,b,c,?,?,?,…”
char arr3[ ] = {‘a’,‘b’,‘c’,‘\0’} ⇒ “abc\0”
2.求字符串长度
strlen("abc");
需要引入头文件<string.h>.
"\0"不计入字符串长度
例:
char arr1[ ] = “abc” 长度为3;
char arr2[ ] = {‘a’,‘b’,‘c’} 长度为随机值;
char arr3[ ] = {‘a’,‘b’,‘c’,‘\0’} 长度为3。
二. 转义字符
转变了原有的意义
转义字符计入字符串长度
例:
①
printf(“c :\ test \ test.c”); ⇒ “c : ert ert.c”
②
printf(“ab \ ncd”); ⇒
ab
cd
“\t"和”\n"均为转义字符,转变了原本想表达的意思
解决方案:在转义字符前加“\”便恢复原意
补充
“\ddd”,ddd表示三个八进制数字(如:\130x);
“\xdd”,dd表示2个十六进制数字(如:\x300).
补充
“%s”打印一串字符
“%c”打印一个字符
三. 注释
用来解释复杂代码或忽略无用代码
不允许嵌套注释
例:注释的应用
int main ()
{
// int a = 10; //C++注释风格(常用)
/*
int b = 0;
*/ //C语言的注释风格
return 0;
}
第五章 选择语句(if-else 语句)
例:
第六章 循环语句(while 语句)
例:
scanf在读取数据时,如果正常返回的化,返回的是读取到数据的个数;反之,则返回EOF。
第七章 函数&数组
一. 函数
(类似于数学)
如 f(x,y) = x+y =C语言=> Add(x,y) = x+ y ;
例:
一般表达
函数表达
二. 数组
在C99标准之前,数组的大小只能用常量来指定;在C99标准中,引入了变长数组的概念,变长数组允许数组的大小由变量来指定。
一组相同类型元素的集合
例:把10个整型1~10储存起来int main () { int arr[ ] = {1,2,3,4,5,6,7,8,9,19}; return 0; }
例:打印数字1~10
数组由下标来访问,下标由0开始。
第八章 操作符
一. 算数操作符
“ + ”,“ - ”,“ * ”(乘),“ / ”(除),“ % ”(取余,取模)
例:
1.int a = 9 / 2;
printf(“%d\n”,a); ⇒ a = 4;
2.float a = 9 / 2;
printf(“%f\n”,a); ⇒ a = 4;
3.float a 9 / 2.0;
printf(%f\n",a); ⇒ a=4.5;
4.int a = 9 % 2;
printf(“%d\n”,a); ⇒ a=1.
若要执行小数运算,则式中至少有一个小数
当出现
%d % %d
会生成%d %d
,两个%
会编译成一个%
,即%% => %
。若要消除这种错误,则应当修改为%d %% %d
.
二. 移位操作符
“ >> ”(右移)," << "(左移).
例:
int main () { int a = 2; int b = a << 1; //移位操作符移动的是二进制位 printf("%d\n",b); return 0; }
如:
2的二进制位:
00000000 00000000 00000000 00000010
左移:
00000000 00000000 00000000 00000100
左移后为4的二进制位
三. 位操作符
“ & ”(按位与);“ | ”(按位或);“ ^ ”(按位异或)。
四. 赋值操作符
= , += , -= , *= , /= , &= , ^=, |= , >>= , <<=.
例:
a=2
a = a +3 <=> a += 3;
a = a - 5 <=> a -= 5;
a = a << 4 <=> a <<= 4.
五. 单目操作符
只有一个操作数
双目操作符:有两个操作数,如:“ + ”,“ - ” …
1. 种类
!(逻辑反操作符);- (负值); + (正值);& (取地址); sizeof (计算操作符的类型长度);
~ (对一个数的二进制位按位取反); - - (前置、后置 - -); + + (前置、后置 + +);
*(间接访问操作符(引用操作符));( 类型 ) (强制类型转换).
例:“ ! ” 的用途
if ( a )
{ } //如果a为真,操作
if ( !a )
{ } //如果a为假,操作
return 0;
补充:在计算机逻辑中 0 为假,非 0 为真;通常表达为 1。
补充:按位取反
如: 0010110 <=(~)=> 1101001
一个二进制整数的表达有三种,分别为原码、反码和补码;但整数在计算机中存储的是补码。且正整数的原码、反码和补码相同!
如:-1
1
为符号位,不改变!
原码:1
000000 0000000 00000000 00000001
反码:1
1111111 11111111 11111111 11111110
补码:1
1111111 11111111 11111111 11111111(补码 = 反码 + 1)
2.前置、后置++ 的讲解(- - 与其类似)
①前置++:先++,后使用
例:
int main () { int a =10; int b = ++a; printf("%d\n",b); printf("%d\n",a); return 0; }
讲解:“a=10”先“++”,则a变为11; 又因为“b=++a”,则“b=11”此时的“++a”表示“10+1”,即“++a=11”;
且此时的a已加,则a已经变为11
即:先给a的值加,再把加后的a的值给b用,此时a的值也发生变换。
②后置++:先使用,后++
例:
int main () { int a =10; int b = a++; printf("%d\n",b); printf("%d\n",a); return 0; }
讲解:先使用a的值给b,在给a自身“++”,最后使用改变后的自己。
例:强制类型转换
int main () { int a= ( int ) 3.14; printf("%d\n",a); return 0; }
讲解:若不使用会出现报错。因为“int”为整数类型,“3.14”为小数,应使用“float”浮点型才不会出错
六. 关系操作符
种类:> ; >= ; < ; <= ; != ; == ;
七. 逻辑操作符
“ && ” —— 与;“ | | ” —— 或.
(与高中真值表类似)
例:
int main () { int a = 3; int b = 0; int c = a && b; printf("%d\n",c); return 0; } // ⇒ 0 (得出为假)
八. 条件操作符(三目操作符)
AAA ?
BBB :
CCC
若AAA成立,则执行BBB,输出的结果是BBB的结果;
若AAA不成立,则执行CCC,输出的结果是CCC的结果;
例:
普通表达
if ( a> b)
max = a; :
else
max = b;
条件操作符表达
max = a>b ? a : b;
九. 逗号表达式
用“ , ”隔开的一串表达式
例:
int main () { int a = 0; int b = 3; int c = 5; int d = ( a = b + 2,c = a - 4,b = c + 2); printf("%d\n",d); return 0; }
逗号表达式是从左向右依次进行的
逗号表达式的结果是表达式中最后一个表达式的结果
上面例子的计算过程及结果
a = b + 2 = 3 + 2 = 5,
c = a - 4 = 5 - 4 = 1,
b = c + 2 = 1 + 2 = 3.
即输出的结果是 3.
十. 其他
1.下标引用操作符 [ ];
2.函数调用操作符 ( );
3.结构成员 . ->。
第九章 常见关键字
一. 种类
auto (自动的); break ; case ; char (字符类型); const (常变量); continue ; default ; do ; double ; else ; enum (枚举); extern (声明外部符号); float ; for ; goto ; if ; int ; long ; register (寄存器关键字); return ; short ; signed (有符号的); sizeof ; static (静态的); struct (结构); switch ; typedef (类型定义); union (联合体); unsigned ; void ; volatile ; while.
特点:①C语言提供,不能自创;②不能作为变量名。
补充:计算机中,数据的储存位置。
强调:“define”、“include”等不是关键字,是预处理指令!
二. typedef 类型的重定义
是给类型重命名
例:
unsigned int <=(typeder)=> u_int //表达A: int main () { unsigned int num = 100; return 0; } //表达B: typedef unsigned int u_int; int main () { u_int num = 100; return 0; }
A与B等价!
三. static 静态的
1.用途
①修饰局部变量。改变局部变量的生命周期,本质是改变了其存储类型。
②修饰全局变量。使修饰的这个变量只能在该源文件使用;本质是全局变量在其他源文件都可以是因为全局变量具有外部链接属性;但被static修饰后,就变成了内部链接属性,其他源文件就不能链接到这个静态的全局变量。
③修饰函数。使函数只能在自己所在的源文件内部使用,不能在其他源文件内部使用,本质是将函数的外部链接改为内部链接。
2.static 修饰局部变量
void test () //③
{
int a = 1; //②
a++;
printf("%d",a);
}
int main ()
{
int i = 0; //①
while( i < 10 )
{
test( );
i++}
return 0;
}
讲解:先运行①,在运行②,然后输出②的结果,结果为:22222 22222;
若将③处改为 “static int a = 1;” 则结果为:23456 7891011.
**补充:存储类型
3.static 修饰全局变量
源文件A:
int Add(int x,int y)//②
{
return x + y;
}
源文件B
extern int g_val;//①
int main ()
{
printf("%d\n",g_val);
return 0;
}
①处,声明外部符号; (全局变量整个工程都能用)
②处,若改为“static int g_val = 2022;”则文件B无法识别。
4.static 修饰函数
源文件A
int Add (int x,int y)
{
return x + y;
}
源文件B
extern int Add (int x,int y);//①
int main ()
{
int a = 10;
int b = 20;
int sum = Add(a,b);
printf("sum = %d\n",sum);
return 0;
}
①处,若改为“static int Add(int x,int y)”,则源文件B无法识别。
四. register 关键字
第十章 常量&宏
#define 定义常量和宏。
define 是一个预处理指令。
用途:
1.定义符号
#define Max 1000;
int main ()
{
printf("%d\n",Max);
return 0;
}
2.定义宏
宏—— 一个表达式
①:
#define ADD(X,Y) X + Y
int main ()
{
printf("%d\n",ADD(2,3));
return 0;
} //得 5
讲解:2 + 3 = 5
②:
#define ADD(X,Y) X + Y
int main ()
{
printf("%d\n",4 * ADD(2,3));
return 0;
} //得 11
讲解:4 * 2 + 3 = 11
③:
#define ADD(X,Y) ((X) + (Y))
int main ()
{
printf("%d\n",4 * ADD(2,3));
return 0;
} //得 20
讲解:4 * (2 + 3)=20
第十一章 指针
一. 内存
1.内存的编号
32位 —— 32根地址线 —— 物理线 —— 通电 —— 0 / 1;
电信号转换成数字信息,成为1和0组成的二进制序列。
即一个32位内存有 232 个内存单元
2.一个内存单元的空间大小
①.假设一个内存单元是1bit,则232bit = 536870912 byte = 524288kb = 512Mb = 0.5GB
由此可见,一个内存单元是1bit太浪费
最终规定,一个内存空间的大小为1个字节(1 byte = 8 bit )
二. 指针变量
1.定义
存放地址的变量
地址并不唯一确定
int main ()
{
int a = 10; //a在内存中要分配空间——占4个字节
printf("%p\n",&a);//%p——打印地址
int * pa = &a;//pa用来存放地址,故pa称为 指针变量
// * 说明pa是指针变量
//int 说明pa执行的对象是int类型的
return 0;
}
int main ()
{
char ch = 'w';
char* pa = &ch;
return 0;
}
int main ()
{
int a = 10;
int* pa = & a;
*pa = 20; //* 解引用操作,*pa就是通过pa里的地址找到1.
printf("%d\n",a);
return 0;
}
2.指针变量的大小
int main ()
{
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(short*));
return 0;
}
32位处理器得出的是4;64位处理器得出的是8.
指针的大小是相同的
指针是用来表示地址的,指针需要多大空间取决于地址的存储需要多大空间。
32位——32bit——4byte
64位——64bit——8byte
第十二章 结构体
1.定义
用来描述复杂对象
例:描述一个学生姓名、年龄、性别、学号的信息
struct Student { char name[20]; int age; char sex[5]; char id[15]; }
2.创建和初始化
结构体可以让C语言创建新的类型出来
int main ()
{
struct Stu s = {"张三",20,80.5};//结构体的创建和初始化
printf("%s %d %lf\n",s.name,s.age,s.sore);//结构体变量,成员变量
return 0;
}
%lf——打印双精度浮点数;
%s——打印一串字符;
%c——打印字符。
3.结构体指针
int main ()
{
struct Stu s = {"张三",20,80.5};
struct Stu * ps = &S;
printf("1:%s %d %lf\n",(*ps).name,(*ps).age,(*ps).score);//①
printf("2: %s %d %lf\n",(*ps)->name,(*ps)->age,(*ps)->score);//②
return 0;
}
①和②的结果相同
初级部分
第十三章 分支语句和循环语句
一. 前言
C语言是结构化的编程语言,它有顺序结构、选择结构和循环结构三种类型;
C语言中“ ;”隔开的就是一个语句
二. 分支语局(选择结构)
结构示意图
C语言中,0为假,非0为真
1. if语句
① 结构:
例:判断是否成年
int main () { int age = 10; if(age >= 18) printf("成年\n"); else printf("未成年\n"); return 0; }
强调: if - else 语句只能控制一条语句,若想控制多条语句需要带“ { } ”。
例:
int main () { int age = 60; if(age < 18) printf("少年\n"); else if(age >= 18 && age <26) printf("青年\n"); else if(age >= 26 && age < 60) printf("壮年\n"); else printf("老年\n"); return 0; }
表示并列关系不能用 18<age<=26,只能用18<age &&age<=26.
② 悬空 else问题
如:
int main ()
{
int a = 0;
int b = 0;
if(a == 1)//②
if(b == 2)//①
printf("hehe\n");
else
printf("haha\n");
return 0;
} //=> 结果:不打印
讲解:
①处,else之于最近的if相互作用;
②处,该语句无法继续,直接结束。
即,else和与其距离最近的if起作用。
例:判断一个数是否是奇数
int main () { int num = 15; if(num % 2 == 1) printf("是奇数\n"); else printf("不是奇数\n"); return 0; }
例:输出1~100之间的奇数
int main () { int i = 0; for(i=1; i<=100; i++ ) { if(i % 2 == 1) printf("%d\n",i); } return 0; }
2.switch 语句
用于多分支的场景
① 结构
强调:
①必须有停止项;
②有入口(case),也有出口(break).
break的位置由实际情况而定。
例:
int main () { int Subject = 0; scanf("%d\n",&Subject); switch(Subject) { case 1: printf("Chinese\n"); break; case 2: printf("Math\n"); break; case 3: printf("English\n"); break; } return 0; }
例:
iny main () { int day = 0; scanf("%d\n",&day); switch(day) case 1: case 2: case 3: case 4: case 5: printf("workday\n"); break; case 6: case 7: printf("weekday\n"); break; return 0; }
②default子句
(不受位置限制)
结构:
default:
printf("错误\n");
break;
例:
int main () { int n = 1; int m = 2; switch(n)//n=1 { case 1: m++;//m=2+1=3 case 2: n++;//n=1+1=2 case 3: switch(n)//n=2 { case 1: n++; case 2: n++,m++;//m=3+1=4,n=2+1=3; break; } case 4: m++;//m=4+1=5,n=3; break;//跳出,完成 default: break; } printf("m=%d,n=%d\n",n,m); return 0; }
三.循环语句
1.while语句
①结构:
while(表达式)
循环语句
②图示:
例:
int main () { int i = 1;//初始化 while(i <= 10) { printf("%d ",i); i++;//调整部分 } return 0; }
③break的讲解
如:
int main ()
{
int i = 1;
while(i)
{
if(i == 5)
{
break;
printf("%d\n",i);
i++;
}
}
return 0;
}
讲解:
在while循环中,break用于永久破坏循环。
④continue的讲解
如:
int main ()
{
int i = 1;
while(i)
{
if(i == 5)
{
continue;
printf("%d\n",i);
i++;
}
}
return 0;
}
讲解:
在while循环中,continue的作用是跳过本次循环continue后的代码,直接到判断部分,判断是否进行下一次循环。
补充:getchar 和 putchar
getcahr() —— 得到一个字符;
putchar() —— 输出一个字符。
EOF —— end of file —— 文件结束标志例:模拟设置密码
int main () { char password[20]={ 0 }; printf("请输入密码\n"); scanf("%s",password); printf("请确认密码\n"); int tmp = 0; while((tmp=getchar()) != '\n')//getchar()处理'\n',清除缓冲区的多个字符 { ; } int char = getchar(); if(ch == 'Y') printf("确认成功\n"); else printf("确认失败\n"); return 0; }
例:打印数字字符
int main () { int ch = 0; while((ch = getchar()) != EOF) { if(ch<'0' || ch>'9') continue; putchar(ch); } return 0; }
2.for语句
①.结构
for(表达式1(初始化);表达式2(判断部分);表达式3(调整部分))
循环语句
②.图示
例:
int main () { int i = 0; for(i=1; i<=10; i++) { printf("%d ",i); } return 0; }
③.break 与 continue 的应用
如:
int main ()
{
for(i=1; i<=10; i++)
{
if(i == 5)
break;
printf("%d ",i);
}
return 0;
}
int main ()
{
for(i=1; i<=10; i++)
{
if(i == 5)
continue;
printf("%d ",i);
}
return 0;
}
continue 在for语句中不跳出“调整部分”,但在while语句中跳出“调整部分”。
④.注意事项
1)不在for循环体内修改循环变量,防止for循环失去控制;
例:
int main () { int i = 0; for(i=1; i<=10; i++)//① { printf("%d ",i); //② } return 0; }
1) 若①处的“i++”删除添加到②处,则可正常进行
2) 若①处和②处均有调整部分,则会使for语句失去控制
2)for语句的循环控制变量的取值采用“前闭后开区间”写法。
for(i=0; i<=9; i++)
左闭右闭区间
for(i=0; i<10; i++)
左闭右开区间
3)若判断部分省略,则判断部分恒为真。
例1:
int main ()
{
int i = 0;
int j = 0;
for(i=0; i<3; i++)//①
{
for(j=0; j<3; j++)//②
{
printf("hehe\n");
}
}
return 0;
}
若三处①②处的“i=0”和“j=0”,则输出3个“hehe”。
例2:
int main ()
{
int i = 0;
int k = 0;
for(i=0, k=0; k=0; i++, k++)//k为假
{
k++;
}
return 0;
}
判断为假,不循环。
3. do…while 语句
①结构
do
循环语句;
while(表达式);
②图示
例:
int main ()
{
int i = 0;
do
{
printf("%d ",i);
i++;
}while(i<=10);
return 0;
}
③break 与 continue 的运用
例:
int main ()
{
int i = 1;
do
{
if(i == 5)
break;
printf("%d\n",i);
i++;
}while(i<=10);
return 0;
} //⇒ 1 2 3 4
例:
int main ()
{
int i = 1;
do
{
if(i == 5)
continue;
printf("%d\n",i);
i++;
}while(i<=10);
return 0;
} //⇒ 1 2 3 4 ……(死循环)
循环至少执行一次
4. goto语句
C语言中提供了可以随意使用的goto语句和标记跳转标号。goto语句最常见的用法就是终止程序在某些深度嵌套的结构的处理过程。
语法:goto "标号";
示例:
实现一个函数:若输入指定字符串则不强制关机;若输入非指定字符串或
不输入,则在60秒后自动关机
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<windows.h>
int main()
{
char arr[10] = { 0 };
system("shutdown -s -t 60");//设置60秒后关机
//输入部分
again:
printf("输入“我是猪”则取消自动关机:");
scanf("%s", &arr);//接受输入字符串
//判断字符串是否合法
if (strcmp(arr, "我是猪") == 0)
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
四. 分支语句和循环语句的综合应用
1.计算n的阶乘。
```cpp
int main()
{
int i = 0;
int n = 0;
int ret = 1;//阶乘从1开始,因为0乘任何数均为0
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
ret *= i;
}
printf("%d\n", ret);
return 0;
}
2.计算1!+2!+3!+ …… +9!+10!
①普通方案
int main()
{
int i = 0; int n = 0;
int ret = 1; int sum = 0;
for (n = 1; n <= 10; n++)
{
ret = 1;
for (i = 1;i <= n; i++)
{
ret *= i;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
②优化方案
int main()
{
int i = 0; int n = 0;
int ret = 1; int sum = 0;
for (n = 1; n <= 10; n++)
{
ret *= n;
sum += ret;
}
printf("%d\n", sum);
return 0;
}
3. 在一个有序数列数列中找出具体的某个数字
折半查找(二分查找法)
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 7;//要查找的数字
int sz = sizeof(arr) / sizeof(arr[0]);//数组的元素个数
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid + 1;
}
else
{
printf("找到了,下标是:%d\n", mid);
break;
}
if (left > right)
{
printf("找不到");
}
}
return 0;
}
4. 编写代码,演示多个字符从两端向中间汇聚
int main()
{
char arr1[] = "I also missed you in Xianyang";
char arr2[] = "#############################";
int left = 0;
int right = strlen(arr1) - 1;
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep (500);
system("cls");
left++;
right--;
}
printf("%s\n", arr2);
return 0;
}
Sleep() 函数—暂停——单位:毫秒ms—头文件<windows.h>.
system(“cls”)—清空屏幕。
5. 编写代码模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录完成;如果三次均输入错误则退出程序)
int main()
{
int i = 0;
char password [20] = { 0 };
for (i = 0; i < 3; i++)
{
printf("请输入密码:");
scanf("%s", password);
if (strcmp(password, "123456") == 0)
{
printf("登陆成功!\n");
break;
}
else
{
printf("密码错误,请重新输入!\n");
}
}
if (i == 3)
printf("三次密码错误,退出程序!\n");
return 0;
}
字符串比较不能用“ == ”只能用strcmp函数比较!
6. 猜数字游戏(1-100内)
①自动生成1~100之间的随机数;
②方案:
③代码:
//游戏菜单
void menu()
{
printf("#######################\n");
printf("###### 1.开始游戏 #####\n");
printf("###### 0.退出游戏 #####\n");
printf("#######################\n");
}
//游戏内容
void game()
{
//1.生成随机数
int ret = rand() % 100 + 1;
//rand函数返回的是一个0-32767之间的数字
//时间->时间戳
//%100的余数是0-99,然后+1范围便是1-100
//2.猜随机数
int guess = 0;
while (1)
{
printf("请猜数字:");
scanf("%d", &guess);
if (guess < ret)
{
printf("猜小了\n");
}
else if (guess > ret)
{
printf("猜大了\n");
}
else
{
printf("恭喜你,猜对了!!!\n");
break;
}
}
}
//游戏框架
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();//打印菜单
printf("请选择(1/0):\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏\n");
break;
}
} while (input);
return 0;
}
五. 循环语句中break,continue的作用总结
break:
在while、for、do…while语句中,break的作用都是直接终止循环语句。
continue:
while语句 —— 终止本次循环,continue后的代码不再执行,开始下一次循环;
for语句 —— 终止本次循环,continue后的代码不再执行,但不跳过调整部分,在调整后加入下一次循环;
do…while语句 —— 仅跳过continue后的代码,但判断部分不跳过。
第十四章 函数
一. 定义
子程序:某一大型程序的部分代码,具有一定独立性。
分类:库函数;自定义函数。
二. 库函数
为了简化流程而提升效率
库函数的学习途径
① www.cplusplus,com
② MSDN
③ http://en.cppreference.com (英文版)
④ http://zh.cppreference.com (中文版)
常用库函数
①IO函数——输入输出函数:printf;scanf;getchar;push;
②字符串操作函数:strmp;strlen;
③字符操作函数:toupper;
④内存操作函数:memcpy;memcmp;memset;
⑤时间操作函数——日期操作函数:time;
⑥数学函数:sqrt;pow;
⑦其他函数。
库函数使用必须引用相应的头文件
例:memset函数
int main () { char arr[] = "helloworld"; memset(arr, ' x ', 5); printf("%s\n",arr); return 0; }
三. 自定义函数
**1.结构 **
函数的返回类型 函数名(函数的参数)
{
函数体(语句项);
}
2.实例
①找出两个整数的最大值
int get_max(int x, int y)//自定义函数
{
int z = 0;
if (x < y)
z = y;
else
z = x;
return z;
}
int main()//主函数体
{
int a = 20;
int b = 10;
int max = get_max(a, b);
printf("max=%d", max);
return 0;
}
②写一个交换两个变量整型的值的函数
void swap(int *pa,int *pb)
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 20;
int b = 10;
printf("交换前:a=%d b=%d", a, b);
swap(&a, &b);
printf("交换后:a=%d b=%d", a, b);
return 0;
}
用指针使主函数与swap函数产生地址与数值上的联系
四. 函数的参数
1.实际参数(实参)
真实传给函数的参数;都必须有确定的值。
如:常量、变量、表达式、函数……
2.形式参数(形参)
函数名后括号中的变量;只有在函数调用的过程中才有效,函数调用完后自动销毁。
例:
void swap(int* pa, int* pb)//①函数定义;形式参数 { int z = 0; z = *pa; *pa = *pb; *pb = z; } int main() { int a = 20; int b = 10; printf("交换前:a=%d b=%d\n", a, b); swap(&a, &b);//②函数调用—传值调用;实际参数 printf("交换后:a=%d b=%d", a, b); return 0; }
五. 函数的调用
1.传值调用
函数的形参和实参分别占用不同的内存块,对形参的修改不会影响实参。
2.传址调用
①把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式;
②传址调用可以让函数和函数外部的变量建立起真正的联系;函数内部可以直接改变外部变量。
3.实例
①写一个函数,列出1~200间的素数
int is_prime(int n)
{
int j = 0;
for (j = 2;j < n;j++)
{
if (n % j == 0)
return 0;
}
return 1;
}
int main()
{
int i = 0;
int count = 0;
for (i = 100;i <= 200;i++)
{
if (is_prime(i) == 1)
{
count++;
printf("%d ", i);
}
}
printf("\ncount=%d\n", count);
return 0;
}
②写一个函数,列举出1000年~2000年的闰年
int is_leap_year(int n)
{
if ((n % 4 == 0 && n % 100 != 0) || (n % 400 == 0))
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int y = 0;
for (y = 1000;y <= 2000;y++)
{
if (is_leap_year(y) == 1)
{
printf("%d ", y);
}
}
return 0;
}
若不写返回类型会默认为“int型”,但不建议不写——标准化,规范化。
③写一个函数,实现一个整数有序数组的二分查找
int binary_search(int a[], int k,int s)
{
int left = 0;
int right = s - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (a[mid] > k)
{
right = mid - 1;
}
else if(a[mid] < k)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int key = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
int ret = binary_search(arr, key, sz);
if (-1 == ret)
{
printf("找不到\n");
}
else
{
printf("找到了,下标是:%d\n", ret);
}
return 0;
}
数组arr传参,实际传的不是数组本身而是传过去了数组的首元素地址;再以参数的形式传递过去。
函数内部变量改变外部变量需要地址和相应的指针;
&a(取地址) ——> *a(传指针)。
④写一个函数,每调用一次num的值便会加1
void add(int* n)
{
(*n)++;
}
int main()
{
int num = 0;
add(&num);
printf("%d\n",num);
add(&num);
printf("%d\n", num);
return 0;
}
六. 函数的嵌套调用和链式访问
函数与函数之间是可以有机合成的
1.嵌套调用
void test3()
{
printf("h");
}
int test2()
{
test3();
return 0;
}
int main()
{
test2();
return 0;
}
2.链式访问
把一个函数的返回值直接作为另一个函数的参数
int main()
{
printf("%d\n", strlen("abc"));
return 0;
}
注意!
int main () { printf("%d ",printf("%d ",printf("%d ",43))); return 0; }// ⇒ 4 3 2 1
printf函数在其他函数里作参数时,打印的时函数里字符的个数;
若在最外侧的时候则打印内容。
七. 函数的声明和定义
先定义(或声明)再使用函数;函数的声明一般放在头文件中。
int main()
{
int a = 10;
int b = 20;
//函数声明
int add(int, int);
int c = add(a, b);
printf("%d\n", c);
return 0;
}
int add(int x,int y)//函数的定义
{
return x + y;
}
八.函数递归
1.定义
一个函数和过程在其定义或说明中有直接或间接调用自身的一种方法。(自己调用自己)
例:
int main () { printf("hehe\n"); main (); return 0; } //⇒ 死循环
2.条件
①存在限制条件,当满足这个限制条件的时候递归便不再继续;
②每次递归之后越来越接近这个条件。
3.实例
①接受一个整型值,按照顺序打印它的每一位
void print(unsigned int n)
{
if (n > 9)//控制递归的起止,判断条件
{
print(n / 10);//限制语句
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%u", &num);
print(num);
return 0;
}
不能死递归,都有跳出条件,每次跳出要逼近条件;
递归层次不能太深。
②编写函数不允许创建临时变量,求字符串长度
方案1:
int my_strlen(char *str)
{
int count = 0;
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "bit";
printf("%d\n", my_strlen(arr));
return 0;
}
方案2:
int my_strlen(char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "bit";
printf("%d\n", my_strlen(arr));
return 0;
}
补充:stackoverflow(栈溢出)
3. 递归与迭代
实例1:求n的阶乘
方案1:
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
int ret = 1;
for (i = 1;i <= n;i++)
{
ret *= i;
}
printf("%d\n", ret);
return 0;
}
方案2:
int Fac(int n)
{
if (n <= 1)
return 1;
else
return n * Fac(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fac(n);
printf("%d\n", ret);
return 0;
}
实例2:求第n个斐波那契数
方案1:
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
方案2:
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
第十五章 数组
一. 定义
一组相同类型元素的集合
二. 一维数组
1. 一维数组的创建方式
type_t arr_name [const_n]
type_t 数组的元素类型;const_n 常量表达式,用来指定数组的大小。
arrp[ n ] 变长数组——数组的参数是变量。
2.一维数组的初始化
int a = 10;
初始化;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
完全初始化;
int arr[10] = {1,2,3,4,5}
不完全初始化;
int arr[ ] = {1,2,3,4}; 与 int arr[4] = {1,2,3,4}; 等价!
③一维数组的使用
如:
int main()
{
int arr[10] = { 0 };
arr[4] = 5;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
1)数组是使用下标来访问的,下标是从0开始;
2)数组的大小可以通过计算得到。( int sz = sizeof(arr) / sizeof(arr[0]) )
[ ] 下标引用操作符。
4. 一维数组在内存中的存储
①一维数组在内存中是连续存放的;
②随着数组下标的增长,地址是由低到高变化的。
实例:
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *p); p++; } return 0; }
%x —— 十六进制打印; %p —— 按地址的格式打印—十六进制打印。
三. 二维数组
1. 二维数组的创建
int main ()
{
int arr[3][4];//三行四列数组
return 0;
}
结果:
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};
不完全初始化后补0,②;
int arr[3][4] = {{1,2},{3,4},{4,5}};
③;
int arr[ ][4] = {{1,2},{3,4},{4,5}};
只有行数可省略。
①结果:
③结果:
3. 二维数组的使用
int main()
{
int arr[3][4] = { {1,2},{3,4},{4,5} };
int j = 0;
int i = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
4. 二维数组的存储形式
①二维数组在内存中是连续存放的;
②单个行内部是连续的,跨行间也是连续的。
图示:
强调:“arr[2][3]”中,"arr[2]"为函数名!
③实例
int main()
{
int arr[3][4] = { {1,2},{3,4},{4,5} };
int j = 0;
int i = 0;
int* p = &arr[0][0];
for (i = 0; i < 12; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
4. 数组作为函数参数
实例:冒泡排序
①排序思想:两两相邻的元素进行比较并且可能的话进行交换。
确定趟数 = 个数 - 1.
元素个数的计算“int sz = sizeof(arr) / sizeof(aee[0]);”不能在自定义函数中,应当在主函数中,然后以参数的形式传至自定义函数中。
函数在传参时,传递的时数组的首元素地址。
②代码:
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
五. 数组名
首元素的地址
例外情况:
1.sizeof 数组名 —— 其中数组名表示整个数组,计算的是整个数组的大小;
2.& 数组名 —— 表示的是整个数组,取出的是整个数组的地址。
六. 数组的应用
1. 三子棋游戏
①大纲
数组作为函数传参时,形参可以写成两种形式:
1.数组形式 —— test(int arr[10]);
2.指针形式 —— test(int *arr).
②代码
game.h
#pragma once
//头文件的包含
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//符号定义
#define ROW 3
#define COL 3
//函数的声明
初始化棋盘的函数
void InitBoard(char board[ROW][COL], int row, int col);
打印棋盘的函数
void DisplayBoard(char board[ROW][COL], int row, int col);
玩家下棋的函数
void PlayerMove(char board[ROW][COL], int row, int col);
电脑下棋的函数
void ComputerMove(char board[ROW][COL], int row, int col);
//判断结果
//1.玩家赢------*;
//2.电脑赢------#;
//3.平局--------Q;
//4.游戏继续----C。
//判断是否有输赢
char IsWin(char board[ROW][COL], int row, int col);
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("************************\n");
printf("******** 1.Play ********\n");
printf("******** 2.Exit ********\n");
printf("************************\n");
}
void game()
{
//存储数据--二维数组
char board[ROW][COL];
//初始化棋盘--初始化空格
InitBoard(board, ROW, COL);
//打印一下棋盘--本质是打印数组的内容
DisplayBoard(board, ROW, COL);
char ret = 0;//接受游戏状态
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断玩家输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
break;
//电脑下棋
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断电脑输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*')
{
printf("玩家赢了!!!!!\n");
}
else if (ret == '#')
{
printf("电脑赢了!!!!!\n");
}
else
{
printf("平局!!\n");
}
DisplayBoard(board, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for(j = 0;j < col; j++)
{
board[i][j]=' ';
}
}
}
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for(j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
{
int j = 0;
for(j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家走:>\n");
while (1)
{
printf("请输入下棋的坐标:>");
scanf("%d %d", &x, &y);
//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//下棋
//判断坐标是否被占用
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入!\n");
}
}
else
{
printf("坐标错误,请重新输入!\n");
}
}
}
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑走:>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
//判断坐标是否被占用
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int IsFull(char board[ROW][COL], int row, int col)
{
int j = 0;
int i = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;//棋盘没满
}
}
}
return 1;//棋盘满了
}
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
//判断三行
for (i = 0; i < col; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
//判断三列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
}
//判断对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//判断平局
//如果棋盘满了返回1,不满返回0
int ret = IsFull(board, row, col);
if (ret == 1)
{
return 'Q';
}
//继续
return 'C';
}
2. 扫雷游戏
①大纲
②代码
game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//显示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("**************************\n");
printf("********* 1.Play *********\n");
printf("********* 2.Exit *********\n");
printf("**************************\n");
}
void game()
{
//存放雷的信息
char mine[ROWS][COLS] = { 0 };
//存放排查出的雷的信息
char show[ROWS][COLS] = { 0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');//无雷
InitBoard(show, ROWS, COLS, '*');//有雷
//布置雷
SetMine(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
default:
printf("选择错误,重新选择\n");
}
} while (input);
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("------------------------------\n");
for (i = 0; i <= 9; i++)
{
printf("%d ", i);
}
printf("\n");
for(i = 1; i <= row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("------------------------------\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
//随机生成下标(雷)
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
count--;
}
}
}
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return(mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功!!!\n");
DisplayBoard(mine, ROW, COL);
}
}
第十六章 操作符&表达式
一. 算术操作符
1.int a = 9 / 2;
printf(“%d\n”,a); ⇒ a = 4;
2.float a = 9 / 2;
printf(“%f\n”,a); ⇒ a = 4;
3.float a 9 / 2.0;
printf(%f\n",a); ⇒ a=4.5;
4.int a = 9 % 2;
printf(“%d\n”,a); ⇒ a=1.
若要执行小数运算,则式中至少有一个小数;
%两侧必须是整数。
二. 移位操作符
移动二进制位
如:
2的二进制位:
00000000 00000000 00000000 00000010
左移:
00000000 00000000 00000000 00000100
左移后为4的二进制位
左移操作符:
左边丢弃,右边补0;
右移操作符:
1.算术右移:右边丢弃,左边补原符号位;
2.逻辑右移:右边丢弃,左边补0.
三. 位操作符
&(按位与) ; |(按位或) ; ^(按位异或)
按二进制位
1. &
有0则0,全1才1。
例:3 & 5 = 1
3::00000000 00000000 00000000 00000011;
5:00000000 00000000 00000000 00000101;
1:00000000 00000000 00000000 00000001.
2. |
有1则1,全0才0.
例:3 | 5 = 7
3: 00000000 00000000 00000000 00000011;
5: 00000000 00000000 00000000 00000101;
7: 00000000 00000000 00000000 00000111;
3. ^
相同为0,相异为1.
例:3 ^ 5 = 6
3: 00000000 00000000 00000000 00000011;
5: 00000000 00000000 00000000 00000101;
6: 00000000 00000000 00000000 00000110;
4. 用途
实例:交换两个int变量的值,不能使用第三个变量
即a=3,b=5交换之后a=5,b=3.
int main()
{
int a = 3;
int b = 5;
//①
int c = 0;
c = a;
a = b;
b = c;
//②
a = a + b;
b = a - b;
a = a - b;
//③
a = a ^ b;
b = a ^ b;
a = a ^ b;
return 0;
}
a ^ a = 0; 0 ^ a = a;b=a ^ a ^ b=a ^ b ^ a
四. 赋值操作符
“=” —— 赋值;
“==” —— 判断相等。
五. 单目操作符
1. 逻辑反操作符——“ ! ”
2. " - "负值;“ + ”正值
一般“ + ”省略。
3. “ sizeof ”计算统计操作数的类型长度,单位:字节。
4. “ ~ ”对一个数的二进制位按位取反
包括符号位!
5. “ + + ”“- -”
前置 ++(- -):先运算,再使用;
后置 ++(- -):先使用,再运算。
6. &——取地址;*——间接访问操作符(解引用操作符);%p——打印地址。
7. (类型)—— 强制类型转换
六. 关系操作符
<;<=;>;>=;!=;==;
七. 逻辑操作符
&&;||
八. 条件操作符
__ ? __ : __
九. 逗号表达式
__ , __ , __ , ……
十. 其他操作符
1. [ ] 下标引用操作符;
2. ( ) 函数调用操作符;
3.“ . ”“ -> ” 结构成员。
第十七章 指针
一. 定义
地址 <==> 指针
内存编号 —— 地址(指针变量) —— 指针
指针大小
在32位平台是4个字节;在64位平台是8个字节。
例:
int main () { int a = 10;//a 占四个字节 char *pa = &a;//拿到的是a的4个字节中第一个字节地址 *pa = 20;//指针变量 return0; }
二. 指针和指针类型
1. 指针类型的意义
①指针类型决定了指针解引用的权限有多大;
int main()
{
char a = 0 * 11223344;
char* pc = &a;
*pc = 0;
int b = 0 * 55667788;
int* pb = &b;
*pb = 0;
return 0;
}
②指针类型决定了指针走一步能有多长。
int main()
{
int arr[10] = { 0 };
int* p = arr;
int* pc = arr;
printf("%p\n", p);
printf("%p\n", pc);
printf("%p\n", p+1);
printf("%p\n", pc+1);
return 0;
}
三. 野指针
指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1. 成因
①指针未初始化;
int main ()
{
//这里的p是野指针
int *p;//p是一个局部变量,局部变量不初始化的化默认是随机值
*p = 20;//非法访问内存
}
②越界访问
int main ()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0;i<=0;i++)
{
*p = i;
p++;
}
return 0;
}
③非法访问内存(指向的空间被释放)
int test ()
{
int a = 10;
return &a;
}
int main ()
{
int *p = test();
*p = 20;
return 0;
}
2. 如何避免野指针
①指针初始化
1)当不知道初始化为什么时,初始化为NULL(空指针);
2)明确知道初始化的值。
②小心指针越界;
③指针释放的指向空间失效后,及时释放NULL;
④指针使用之前检查有效性。
四. 指针运算
1. 指针 ± 整数
#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[0]; vp < &values[N_VALUES];)//vp < &values[N_VALUES] 指针关系运算
{
*vp++;
}
2. 指针 - 指针
得到的是两个指针间元素的个数。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
3. 指针的关系运算
实例:
for(vp=&values[N_VALUES];vp>&values[0];)
{
*--vp=0;
}
五. 指针和数组
数组名是数组首元素的地址
六. 二级指针
指针变量的存放地址
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
printf("%p\n", *pa);
printf("%p\n", *ppa);
return 0;
}
七. 指针数组
本质上是一个数组
int main()
{
int arr[10];//整形数组
char ch[5];//字符数组
int* parr[5];//指针数组
return 0;
}
第十八章 结构体
一. 结构
结构是一些值的集合;这些值称为成员变量。结构的每个成员可以是不同类型的变量。
实例:
struct Stu //类型
{//成员变量
char name[20];
char id[20];
int age;
}S1,S2;//①
int main ()//对象
{
struct Stu s;//②
return0;
}
①S1,S2也是结构体变量,是全局变量;
②s 是局部变量。
二. 结构体定义和初始化
结构体变量也可当成员变量
结构体嵌套初始化:{,{ }, , ,} = s
三. 结构体成员的访问
通过“ . ”操作符访问的(" -> "也可以)
如:
s . a . b 或 s -> a -> b(正推)
(*ps). sb . c 或 ps -> sb . c(逆推)
“ -> ”用于指针,“ . ”用于普通的
四. 结构体传参
实例:
struct B
{
char c;
short s;
double d;
};
struct Stu
{
struct B sb;
char name[20];
int age;
char id[20];
};
void print1(struct Stu t)//传值调用
{
printf("%c %d %lf %s %d %s\n", t.sb.c, t.sb.s, t.sb.d, t.name, t.age, t.id);
}
void print2(struct Stu *ps)//传址调用
{
printf("%c %d %lf %s %d %s\n", ps->sb.c, ps->sb.s, ps->sb.d, ps->name, ps->age, ps->id);
}
int main()
{
struct Stu s = { {'w',20,3.14},"张三",30,"2012"};
print1(s);
print2(&s);
return 0;
}
补充:函数传参时,选用传址调用的原因
函数传参的时候,参数是需要压栈的;若传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能下降。
故结构体传参的时候要传递结构体的地址。参数压栈
栈 —— 数据结构 —— 先进后出,后进先出
第十九章 调试技巧
一. bug
程序错误、缺陷。
二. 调试(debug)
步骤:
发现程序错误的存在;
以隔离、消除等方法对错误进行定位;
确定错误产生原因;
提出纠正错误的解决方法;
对错误予以纠正,重新测试。
三. debug & release
1. debug
调试版本,包含调试信息且不做任何优化,便于程序员调试程序。
2.release
发布版本,往往进行了各种优化,使程序在代码大小和运行速度都是最优的,以便用户更好使用。
四. windows环境调试
(Linux环境调试工具为gdb)
1.调试环境准备
debug
2.快捷键
F5 —— 启动调试,用来直接跳到下一个断点处;
F9 —— 创建/取消 断点;
断点 —— 程序执行的中断处
F10 —— 逐过程,通常来处理一个过程,一个过程可以是一次函数调用或者一条语句;
F11 —— 逐语句,每次执行一条语句,可以进入函数内部执行;
CTRL+F5 —— 执行不调试,使程序直接进行。
3. 调试的时候查看当前的信息。
进阶部分
第二十章 数据的储存
一. 数据类型介绍
char,short,int,long,long long,float,double
使用这些类型开辟内存空间的大小,提供看待内存空间的视角。
二. 类型的基本归类
1. 整型家族
char —— unsigned char 、 signed char;
short —— unsigned short [ int ] 、 signed short [ int ];
int —— unsigned init 、 signed int;
long —— unsigned long [ int ] 、 signed long [ int ].
2. 浮点数家族
float;
double。
3. 构造类型—自定义类型
数组类型;
结构体类型 struct;
枚举类型 enum;
联合类型 union.
4. 指针类型
5. 空类型
void 函数返回类型;
void 函数参数;
void 指针.
三. 整型在内存中的存储
1. 数据在内存中以二进制形式存储;
整数有三种表达形式:原码、反码、补码;
正整数的原码、反码、补码均相同;负整数的原码、反码、补码需要进行计算。
如:
原码(-10为例):1
0000000 00000000 00000000 00001010;
1
符号位:正为0,负为1;
反码(-10为例):1
1111111 11111111 11111111 11110101;
原码的符号位不变,其他位按位取反
补码(-10为例):1
1111111 11111111 11111111 11110110。
反码 + 1得到补码
0101 + 1 = 0102 = 0110(二进制逢二进一)
整数在内存中存储的都是补码。
2. 存补码而不存原码的原因
如:
在计算机中只有加法没有减法。1 - 1 ⇒ 1 + (-1) = 0;
①(1):0
0000000 00000000 00000000 00000001;
②(-1)[原码]:0
0000000 00000000 00000000 00000001;
③(-1)[补码]:1
1111111 11111111 11111111 11111111.
运算过程及结果:
① + ② ⇒ (-2):1
0000000 00000000 00000000 00000010;
① + ③ ⇒ (0): 10
0000000 00000000 00000000 00000000;
多一位,自动抵消,留后32位。
原因:
使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法);此外,补码和原码相互转化,其运算过程是相同的,不需要额外的硬件电路。
四. 大、小端介绍
1. 定义
大端存储模式:数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中。
小端存储模式:数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
大端字节序 & 小端字节序
2.实例:设计一个程序来判断当前设备的字节序
int main()
{
int a = 1;
char* p = (char*)&a;
if (*p == 1)
{
printf("С\n");
}
else
{
printf("\n");
}
return 0;
}
五. 浮点型在内存中的储存
浮点型:float,double,long double;
浮点数:
3.1415926;
1E10 <=> 1.0 * 1010;
1E5 <=> 1.0 * 105.
浮点数范围由<float.h>定义
int main ()
{
int n = 9;//4byte
float *pFloat = (float*)&n;
printf("n的值为:%d\n",n);// ⇒ 9
printf("*pFloat的值为:%f\n",*pFloat);//==> 0,000000
*pFloat = 9.0;//以浮点数的视角储存9.0
printf("n的值为:%d\n",n);// 不是9
printf("*pFloat的值为:%f\n",*pFloat);//==> 9.000000
return 0;
}
1. 任意一个二进制浮点数V可表示为:
- 5.5 —— 十进制
- 101.1 —— 二进制 —— 1.011*102 —— (-1)0 * 1.011 * 22
s=0,M=1.011,E=2
2. 存储大小
①对float而言(32位)
②对double而言(64位)
3. 规定
①M:1.XXXX 不存1,只存小数部分;
②E:无符号整数—正数—中间数表示正负数分界线
1)若E位8位,范围0~255,中间数127;
例:0.1 —— 1.0 * 2-1 —— s=0;M=1.0;E=-1,则-1 + 127 = 126 = E.
2)若E为11位,范围0~2047,中间数1023;
例:0.1 —— 1.0 * 2-1 —— s=0;M=1.0;E=-1,则-1 + 1023 = 1022 = E.
③S = 0 或 S = 1;
4. 强调
①E全为0,表示±0以及接近于0的数(无穷小);
②E全为1,表示±无穷大(M全为0时);
③E不全为0或不全为1,E减127(或1023)得到真实值。
第二十一章 指针
一. 字符指针
char *
1. 指针指向一个字符
例:
int main ()
{
char ch = 'q';
char *pc = &ch;
*pc = 'w';
return 0;
}
2. 指针指向一个字符串
存储字符串首个字符的地址
int main ()
{
char *pstr = "helloworld";
printf("%s\n",pstr);
return 0;
}
3. 实例
int main()
{
char str1[] = "hello qingtian.";
char str2[] = "hello qingtian.";
const char *str3[] = {"hello qingtian."};//①常量字符串
const char* str4[] = {"hello qingtian."};//①常量字符串
if (str1 == str2)
printf("1&2 are same");
else
printf("1&2 are not same");
if(str3==str4)
printf("3&4 are same");
else
printf("3&4 are same");
return 0;
}// ==> str1 and str2 are not same; str3 and str4 are same.
常量字符不能更改
二. 指针数组
存放指针的数组
int *arr1[10]; char *arr2[4]; char **arr3[5].
int main ()
{
int a[5] = {1,2,3,4,5};
int b[5] = {2,3,4,5,6};
int c[5] = {3,4,5,6,7};
int *arr[3] = {a,b,c};
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<5; j++)
{
printf("%d ",*(arr[i]+j));//可换为printf("%d ",arr[i][j]); —— 模拟二维数组
printf("\n");
}
}
return 0;
}
三. 数组指针
1. 定义
能够指向数组的指针
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*parr)[10] = &arr;
printf("%p\n", *parr);
return 0;
}
2.地址
①&数组名 —— 表示数组的地址(表示整体)
&数组名+1 会跳过该数组;
②数组名 —— 表示数组首元素的地址(表示局部)
数组名+1 仅跳过该元素到下一个元素。
数组名是首元素地址的例外情况
- sizeof —— 表示整个数组,计算的是这个数组的大小,单位:字节。
- &数组名 —— 表示整个数组,取出的是整个数组的地址。
3. 实例
(通常在二维数组以上数组使用)
二维数组的首元素表示在数组名,首元素是第一行
void print(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4.5},{2,3,4,5,6},{3,4,5,6,7} };
print(arr, 3, 5);
return 0;
}
int *parr1[10]; 整型指针数组
int arr[5]; 整形数组
int (*parr2)[10]; 数组指针,该指针能指向一个数组
int (*parr3[10])[5]; 存储数组指针的数组;能够存放5个数组指针,每个数组指针能指向一个数组,数组5个元素每个元素都是int类型。
四. 数组参数、指针参数
1. 一维数组传参
void test (int arr[ ])//①
{ }
int main ()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2 (arr2);
return 0;
}
①处可改为:
void test (int arr[10]);
void test (int *arr);
void test2 (int arr[20]);
void test2 (int **arr).
2. 二维数组传参
3. 一级指针传参
void print (int *ptr, int *sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ",*(ptr+i));
}
}
int main ()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
print(p,sz);//p是一级指针
}
4. 二级指针传参
void test (int **p2)
{
**p2 = 20;
}
int main ()
{
int a = 10;
int *pa = &a;//pa是一级指针
int **ppa = &pa;//ppa是二级指针
test(ppa);//①
printf("%d\n",a);
return 0;
}
①处也可改为:
test (&pa) —— 传一级指针变量的地址
test (arr) —— 传存放一级指针数组
五. 函数指针
指向函数的指针,存放函数地址的指针
函数名 <=> &函数名
实例
int Add (int, int)
{
return x+y;
}
int main ()
{
int (*pf)(int, int) = &Add;
return 0;
}
六. 函数指针数组
存放函数指针的数组
实例
int Add(int,int)
{}
int Sub(int,int)
{}
int main()
{
int(*pf1)(int, int) = Add;
int(*pf2)(int, int) = Sub;
int(*pfAr[2])(int, int) = { Add,Sub };
return 0;
}
例:设计一个计算器
int Add(int x, int y)//加
{
return x + y;
}
int Sub(int x, int y)//减
{
return x - y;
}
int Mul(int x, int y)//乘
{
return x * y;
}
int Div(int x, int y)//除
{
return x / y;
}
void menu()
{
printf("*****************************\n");
printf("******* 1.Add 2.Sub *******\n");
printf("******* 3.Mul 4.Div *******\n");
printf("*********** 0.Exit **********\n");
printf("*****************************\n");
}
int main()
{
int input = 0;
do
{
menu();
int(*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入操作数:");
scanf("%d %d", &x, &y);
ret = (pfArr[input])(x, y);
printf("ret=%d\n", ret);
}
else if(input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误!\n");
}
} while (input);
return 0;
}
七. 指向函数指针数组的指针
函数指针的数组 —— 数组
——取地址——>
取出函数指针数组的地址 —— 地址
int (*p)(int, int) 函数指针
int (p2[4])(int, int) 函数指针数组
p3 = &p2 取出的是函数指针数组的地址
int ((*p3)[4])(int, int) = &p2 p3是函数指针数组的指针
八. 回调函数
通过函数指针调用的函数
如果一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这就是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的条件或条件发生时由另一方调用,用于对该事件或条件进行响应。
实例:设计一个计算器
int Add(int x, int y)//加
{
return x + y;
}
int Sub(int x, int y)//减
{
return x - y;
}
int Mul(int x, int y)//乘
{
return x * y;
}
int Div(int x, int y)//除
{
return x / y;
}
void menu()
{
printf("*****************************\n");
printf("******* 1.Add 2.Sub *******\n");
printf("******* 3.Mul 4.Div *******\n");
printf("*********** 0.Exit **********\n");
printf("*****************************\n");
}
int Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入操作数:");
scanf("%d %d", &x, &y);
return pf(x, y);
}
int main()
{
int input = 0;
do
{
menu();
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
ret = Calc(Add);
printf("ret=%d\n", ret);
break;
case 2:
ret = Calc(Sub);
printf("ret=%d\n", ret);
break;
case 3:
ret = Calc(Mul);
printf("ret=%d\n", ret);
break;
case 4:
ret = Calc(Div);
printf("ret=%d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,重新选择!\n");
break;
}
}
while (input);
return 0;
}
九. qsort函数
快速排序
<stdlib.h>
实例
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 0,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
return 0;
}
第二十二章 字符函数&字符串函数&内存函数
一. strlen
用途:求字符串长度。
求长度只求“\0”的长的
看到“\0”才停止下来
例:
int main ()
{
char arr[] = "abc";
int len = strlen(arr);
printf("%d\n",len);
return 0;
}// ==>3
例:模拟strlen
int my_strlen (const char *str)
{
int count = 0;
assert(str != NULL);
while(*str != '\0')
{
count++;
str++;
}
return count;
}
assert 需要头文件<assert.h>
计算表达式,如果它的条件返回错误,则终止程序执行;
用途:确认函数内部参数正确性
函数返回的是 size_t, 是无符号数;无符号数无法比较大小
二. strcpy
结构:strcpy (目的地,来源);
用途:拷贝字符串
int main()
{
char arr[20] = "hello";
strcpy(arr, "qintian");
printf("%s\n", arr);
return 0;
}
拷贝时有’\0’才停止拷贝
强行拷贝,保证空间足够,目的地必须可以修改
三. strcat
结构:strcat (目的地,来源)
用途:字符串追加(连接)
例:
int main()
{
char arr1[20] = "hello ";
char arr2[] = "qintian";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
例:模拟strcat
char* my_strcat(char* dest, char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest)
{
dest++;
}
while (*dest++ = *src--)
{
;
}
return ret;
}
四. strcmp
结构:strcmp (字符串1,字符串2)
用途:比较2个字符串
例:
int main()
{
int ret = strcmp("aaa","aaa");
printf("%d\n",ret);
return 0;
}
例:模拟srcmp
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
if (*s1 > *s2)
{
return 1;
}
else
{
return -1;
}
}
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == *s2)
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
比较ASCII值
五. strncpy
结构:strncpy (目的地,来源,拷贝字符个数)
用途:拷贝有限定的字符
实例:
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "qwer";
strncpy(arr1, arr2, 2);
printf("%s\n", arr1);
return 0;
}
六. strncat
结构:strncat (目的地,来源,连接字符的个数)
用途:连接有限个字符
实例:
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strncat(arr1, arr2, 10);
printf("%s\n", arr1);
return 0;
}
七. strncmp
结构:strncmp (字符串1,字符串2,比较个数)
用途:比较有限个字符是否相同
实例:
int main()
{
char p[] = "abcdef";
char q[] = "abcqwert";
int ret = strncmp(p, q, 4);
printf("%d\n", ret);
return 0;
}
八. strstr
结构:strstr (字符串1,字符串2)
用途:在一个字符串中找另一个字符串
实例:
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("找不到\n");
}
else
{
printf("找到了\n");
}
return 0;
}
模拟strstr
char* my_strstr(const char* str1, const char* str2)
{
assert(str1, str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
if (*str2 == '\0')
{
return (char*)str1;
}
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cp;
}
cp++;
}
return NULL;
}
九. strtok
结构:strtok (字符串1,字符串2)
用途:切割字符串
实例:
int main()
{
char arr[] = "zpw@bitedu.tech hehe";
const char* p = "@.";
char tmp[30] = { 0 };
strcpy(tmp, arr);
char* ret = NULL;
for (ret = strtok(tmp, p);ret != NULL;ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
十. strerror
结构:strerror (内容) <string.h><errno.h>
用途:返回错误码对应的错误信息
实例:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
十一. perror
结构:perror (const char *str)
用途:打印错误信息
实例:
int main ()
{
//打开文件失败的时候会返回NULL
FILE *pf = fopen("test.txt","r");
//返回0,则为“no error”
if(pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
十二. 字符分类函数
如果符合相应条件返回为真
1.iscntrl 任何控制字符;
2.isspace 空白字符:“ 空格 ”,换页" \f “,换行” \n “,回车” \r “,制表符” \t “,垂直制表符” \v ";
3.isdight 十进制数字;
4.isxdight 十六进制数字 0 ~ 9,a ~ f,A ~ F;
5.islower 小写字母;
6.issupper 大写字母;
7.isalpha 大、小写字母;
8.isalnum 字母或数字;
9.ispunct 标点符号,任何不属于数学或字母的图形字符;
10.isgraph 任何图形字符;
11.isprint 任何可打印字符,包括图形和空白字符。
十三. 字符转换函数
<ctype.h>
int tolower (int C); 转小写
int toupper (int c); 转大写
十四. 内存操作函数
1. memcpy
①结构:memcpy (目的地,来源,数量大小)
②用途:内存拷贝
不能拷贝重叠内存
实例:
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = { 0 };
memcpy(arr1, arr2, 20);
return 0;
}
模拟memcpy
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*) dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return 0;
}
2. memmove
用途:内存拷贝
可以拷贝重叠内存
实例:
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
return 0;
}
模拟memmove
void *my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (dest < src)
{
//前到后
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//后到前
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
从低地址向高地址拷贝时,先拷贝高地址,再拷贝低地址;
从高地址向低地址拷贝时,先拷贝低地址,再拷贝高地址。
3. memcmp
结构:memcmp (字符串1,字符串2,内存大小)
用途:内存比较
实例:
int main()
{
float arr1[] = { 1.0,2.0,3.0,4.0 };
float arr2[] = { 1.0,3.0 };
int ret = memcmp(arr1, arr2, 8);
printf("%d\n", ret);
return 0;
}
memcmp与strcmp的返回值相似
4. memset
结构:memset (空间,设置类型,内存大小)
用途:内存设置
实例:
int main()
{
int arr[10] = { 0 };
memset(arr, 1, 20);
return 0;
}
第二十三章 自定义类型
一. 结构体
1. 一些值的集合,结构体的每个成员可以是不同类型。
2. 结构体的声明
例:
struct tag { member_list; }variable_list;
例:
struct book { char name[20]; int price; char id[20]; }b4,b5,b6;//b4,b5.b6是全局变量 int main () { struct book b1;//局部变量 struct book b2;//局部变量 return 0; }
匿名结构体只能用一次
例:匿名结构体类型
struck { char c; int i; char ch; }
例:匿名结构体指针
struck { char c; int i; }*ps;
3. 结构的自引用
typedef struct Node
{
int data;
struct Node* next;
}Node;
4. 结构体变量的定义和初始化
5. 结构体内存对应
计算结构大小
struct S
{
int i;
char c;
};
int main()
{
struct S s = { 0 };
printf("%d\n", sizeof(s));
return 0;
}
①结构体的第一个成员从结构体变量在内存中存储位置的0偏移处开始;
②从第2个成员往后的所有成员都放在一个对齐数的整数倍的地址处;
对齐处:成员的大小和默认对齐数的较小值;
③结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍。
6.修改默认对齐数
7. 结构体传参
二. 位段
1. 定义
2. 位段的声明和定义
与结构相似
①位段的成员必须是int,unsigned int,signed int.
②位段的成员后面有一个冒号和一个数字。
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 50;
};
3. 位段的内存分配
①位段成员可以是int, unsigned int, signed int, char(属于整型家族)类型;
②位段空间上是按照需要以4个字节(int)或1个字节(char)的方式开辟的;
③位段涉及很多不确定因素,位段是不跨平台的,注意可移植的程序应避免使用位段。
位段的跨平台问题:
Ⅰ.int 位段被当成有符号数还是无符号数是不确定的;
Ⅱ.位段中最大位的数目不确定(16位机器最大16,32位机器最大是32;若在32位机器写成27,在16位机器会出现乱码);
Ⅲ.位段中的成员内存是从左向右分配的还是从右向左分配未定义;
Ⅳ.当一个结构包含两个位段,第二个成员位段比较大,无法容纳于第一个位段余位时,是舍弃还是利用余位尚不确定。
跟结构相比,位段可以达到同样的效果,但可以更好的节省空间,但在跨平台使用中存在问题
4. 位段的应用
三. 枚举
1. 定义
enum sex
{
MALE,FEMALE,SECRET
};
2. 枚举的优点
①增加代码的可读性和可维护性;
② 和#define定义的标识符比较枚举类型有利于检查,更加严谨;
③防止命名污染(封装);
④便于调试;
⑤ 使用方便,一次可以定义多个常量。
3. 实例
void menu()
{
printf("***********************\n");
printf("***** 1.add 2.sub *****\n");
printf("***** 3.mul 4.div *****\n");
printf("******* 0.exit ********\n");
printf("***********************\n");
}
enum option
{
exit,
add,
sub,
mul,
div
};
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d ", &input);
switch (input)
{
case add:
break;
case sub:
break;
case mul:
break;
case div:
break;
case exit:
break;
default:
break;
}
} while (input);
return 0;
}
四. 联合(共用体)
1. 定义
变量包含一系列成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。
union Un
{
char c;
int i;
};
2. 联合的特点
①共同用一块存储空间;
②一个联合的大小至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
3. 联合大小的计算
①至少是最大成员的大小;
②当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
五. 应用(通讯录)
1. 要求
①通讯录能够存放100个人的信息;
(每个人的信息:姓名+年龄+性别+电话+住址)
②增加人的信息;
③删除指定人的信息;
④修改指定人的信息;
⑤查找指定人的信息;
⑥排序通讯录的信息。
动态增长版
1.通讯录初始化后,能存放3个人额信息;
2.当空间存满的时候,增加2个信息(3+2+2+2+……)。
当通讯录退出时,把信息写到文件上;
当通讯录初始化时,加载文件信息到通讯录中。
2. 结构
3. 代码
contact.h
#pragma once
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
#define MAX 1000
#define DEFAULT_SZ 3
#define INC_SZ 2
//类型的定义
typedef struct PeoInfo
{
char name[MAX_NAME];
char sex[MAX_SEX];
int age;
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
//通讯录-静态版本
//typedef struct Contact
//{
// PeoInfo data[MAX];//存放添加进来的人的信息
// int sz;//记录的是当前通讯录中有效信息的个数
//}Contact;
//动态版本
typedef struct Contact
{
PeoInfo *data;//指向动态申请的空间,用来存放联系人的信息
int sz;//记录的是当前通讯录中有效信息的个数
int capacity;//记录当前通讯录的最大容量
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人
void AddContact(Contact* pc);
//打印联系人信息
void PrintContact(const Contact* pc);
//删除联系人的信息
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//销毁通讯录
void DestoryContact(Contact* pc);
//保存通讯录信息到文件
void SaveContact(Contact* pc);
//加载文件内容到通讯录
void LoadContact(Contact* pc);
//检测增容的问题
void CheckCapacity(Contact* pc);
contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
//静态版本的
//void InitContact(Contact* pc)
//{
// pc->sz = 0;
// //memset(); - 内存设置
// memset(pc->data, 0, sizeof(pc->data));
//}
//动态版本-初始化
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ*sizeof(PeoInfo));
if (pc->data == NULL)
{
perror("InitContact");
return;
}
pc->sz = 0;//初始化后默认是0
pc->capacity = DEFAULT_SZ;
//加载文件
LoadContact(pc);
}
void DestoryContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}
void SaveContact(Contact* pc)
{
FILE* pf = fopen("contact.dat", "w");
if (pf == NULL)
{
perror("SaveContact");
return;
}
//写文件
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data+i, sizeof(PeoInfo), 1, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
}
void LoadContact(Contact* pc)
{
FILE* pf = fopen("contact.dat", "r");
if (pf == NULL)
{
perror("LoadContact");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
//是否需要增容
CheckCapacity(pc);
pc->data[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
//
// 静态版本的-增加联系人
//void AddContact(Contact* pc)
//{
// if (pc->sz == MAX)
// {
// printf("通讯录已满,无法添加\n");
// return;
// }
// //增加一个人的信息
// printf("请输入名字:>");
// scanf("%s", pc->data[pc->sz].name);
// printf("请输入年龄:>");
// scanf("%d", &(pc->data[pc->sz].age));
// printf("请输入性别:>");
// scanf("%s", pc->data[pc->sz].sex);
// printf("请输入电话:>");
// scanf("%s", pc->data[pc->sz].tele);
// printf("请输入地址:>");
// scanf("%s", pc->data[pc->sz].addr);
//
// pc->sz++;
// printf("增加成功\n");
//}
void CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (ptr != NULL)
{
pc->data = ptr;
pc->capacity += INC_SZ;
printf("增容成功\n");
}
else
{
perror("AddContact");
printf("增加联系人失败\n");
return;
}
}
}
//动态版本- 增加联系人
void AddContact(Contact* pc)
{
//考虑增容
CheckCapacity(pc);
//增加一个人的信息
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("增加成功\n");
}
void PrintContact(const Contact* pc)
{
int i = 0;
//打印标题
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
//打印数据
for (i = 0; i < pc->sz; i++)
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
static int FindByName(Contact* pc, char name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;//找不到
}
void DelContact(Contact* pc)
{
char name[MAX_NAME] = {0};
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
printf("请输入要删除人的名字:>");
scanf("%s", name);
//1. 查找要删除的人
//有/没有
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
//2. 删除
int i = 0;
for (i = pos; i < pc->sz-1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void SearchContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字:>");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要查找的人不存在\n");
return;
}
else
{
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
//打印数据
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
void ModifyContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入要修改人的名字:>");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要修改的人不存在\n");
return;
}
else
{
printf("请输入名字:>");
scanf("%s", pc->data[pos].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pos].age));
printf("请输入性别:>");
scanf("%s", pc->data[pos].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pos].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
//通讯录-静态版本
//1.通讯录中能够存放1000个人的信息
//每个人的信息:
//名字+年龄+性别+电话+地址
//2. 增加人的信息
//3. 删除指定人的信息
//4. 修改指定人的信息
//5. 查找指定人的信息
//6. 排序通讯录的信息
//
//版本2:
//动态增长的版本
//1>通讯录初始化后,能存放3个人的信息
//2>当我们空间的存放满的时候,我们增加2个信息
//3+2+2+2+...
//
//
//版本3
//当通讯录退出的时候,把信息写到文件
//当通讯录初始化的时候,加载文件的信息到通讯录中
//
#include "contact.h"
void menu()
{
printf("********************************\n");
printf("****** 1. add 2. del ******\n");
printf("****** 3. search 4. modify*****\n");
printf("****** 5. sort 6. print *****\n");
printf("****** 0. exit *****\n");
printf("********************************\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
int main()
{
int input = 0;
//创建通讯录
Contact con;//通讯录
//初始化通讯录
//给data申请一块连续的空间再堆区上
//sz=0
//capacity 初始化为当前的最大的容量
InitContact(&con);
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
//增加人的信息
AddContact(&con);
break;
case DEL:
//删除
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SORT:
//自己完善
break;
case PRINT:
PrintContact(&con);
break;
case EXIT:
//保存信息到文件
SaveContact(&con);
//销毁通讯录
DestoryContact(&con);
printf("退出通讯录\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
第二十四章 动态内存管理
一. 动态内存函数
malloc, free, caloc, realoc.
二. 动态内存常见错误
①对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值为NULL,则错误
free(p);
}
②对动态内存的越界访问
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0;i < 40;i++)
{
*(p + i) = i;
}
return 0;
}
③使用free释放非动态内存开辟的空间
int main()
{
int arr[10] = { 0 };//栈区
int* p = arr;
//使用
free(p);//使用free释放非动态内存空间
p = NULL;
return 0;
}
④使用free释放动态内存的一部分
int main()
{
int* p = malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0;i < 5;i++)
{
*p++ = i;
}
return 0;
}
⑤对同一块动态内存开辟的空间多次释放
int main()
{
int* p = (int*)malloc(100);
//使用
//释放
free(p);
//释放
free(p);return 0;
}
⑥动态内存开辟时忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
动态开辟空间的2种回收方式
- 主动free;
- 程序结束。
三. 柔性数组
1. 定义
结构中的最后一个元素允许是未知大小的数,这就是柔性数组的成员。
typedef struct st_type
{
int i;
int a[0];
}type_a;
2. 柔性数组特点
①结构中柔性数组成员前面必须至少是一个其他成员;
②sizeof返回类型的这种结构大小不包括柔性数组的内存;
③包含柔性数组成员的结构用malloc()函数进行动态内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
第二十五章 文件
程序文件 数据文件
一. 文件名
结构:文件路径 + 文件名主干 + 文件后缀。
二. 文件的打开和关闭
1.文件指针
在计算机中,每一个文件在使用过程中均会开辟一个文件信息区用来存放文件的相关信息。这些信息会存放在一个结构体中。该结构体的类型为FILE
,其指针为FILE *
.
我们可以通过文件指针来找到对应的文件。
2. 文件的打开与关闭
打开文件语法:
FILE * fopen("文件名",“打开方式”)
;
关闭文件语法:
FILE * fclose(FILE * stream)
。
常用打开方式
打开方式 | 含义 | 如果文件不存在的操作 |
---|---|---|
r (只读) | 为了输入数据,打开一个已经存在的文本文件 | 报错 |
w (只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
a (追加) | 向文本文件尾部添加数据 | 建立一个新文件 |
- 示例:
三. 文件的随机读写
1. fseek
用途: 根据文件指针的为位置和偏移量来定位文件指针。
语法: int fseek(FILE *stream,"偏移量",起始位置)
2. ftell
用途: 返回文件指针相对于起始位置的偏移量。
语法: long int ftell(FILE * stream)
3. rewind
用途: 让文件指针回到起始位置。
语法: void rewind(FILE * stream)
—— writing by Pan Qifan(潘琦藩) ——