计算机中的程序:为了让计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合。
算法:算法是一个定义明确的计算过程,可以以一些值或一组值作为输入并产生一些值或一组值作为输出。因此算法就是将输入转为输出的一系列计算步骤。
(1) 枚举法(穷举法)
(2) 迭代法
(3) 递归法
C语言应用范围:文字处理程序及电子表格,编译器,操作系统,网络游戏等等。
C语言特点:
1.结构化的程序设计语言
(1)层次清晰,便于按模块化的方式组织程序,易于调试和维护
(2)结构化的缺点也很明显,程序的可重用性差
2.语言简洁,灵活方便
3.可移植性好(C语言的编译程序便于移植)
4.功能强大(既可用于系统软件开发,也适用于应用软件开发)
C语言程序的基本结构:
① main()函数是c程序处理的起点/入口(只有一个)
② main()函数可以返回一个值,也可以不返回值。如果某个函数没有返回值,那么在他前面就是关键字void
注:C语言程序中的一个语句可以跨越多行,并且用分号通知编译器该语句已结束。
1.编译:
· 形成目标代码/文件。目标代码是编译器的输出结果,常见扩展名为“.o”或“.obj”
2.连接:
· 将目标代码跟C函数库相连接,并将源程序所用的库代码与目标代码合并形成最终可执行的二进制机器代码(可执行程序)
3.执行:
· 在待定的及其环境下运行c应用程序
计算机执行程序时,存放组成程序的指令和程序所操作的数据的地方叫计算机的内存,也成为了主存或随机访问存储器
变量:
计算机中一块特定的内存空间(一个数据存储空间的表示),由一个或多个连续的字节组成;不同数据存入具有不同内存地址的空间,相互独立。
变量的命名:
通过变量名可以简单快速地找到在内存中存储的数据。
C语言变量名命名规则:
①C语言规定变量名(标示符)只能由字母、数字、下划线3中字符组成,且第一个字符必须为字母或下划线;
②变量名不能包含除_以外的任何特殊字符。如:%、#、逗号,空格等;
③不可以使用保留字。
char字符型变量,char类型是否有符号取决于编译器。
eg:声明及初始化
char sex = ‘m’;
转义字符:
scanf函数:
scanf是C函数库中最通用的一个函数,可以读取不同格式的数据。
#include
int main()
{
int num;
printf("请输入数量:");
scanf("%d",&num);
return 0;
}
· (1)自动类型转换(隐式类型转换)(系统自动实现):
原则:把表示范围小的类型的值转换到表示范围大的类型的值(将短的数据类型转换为长的数类型,精度低的向精度高的转换,提高运算精度)。
short(char)->int->unsigned->long->float->double
· (2)强制类型转换(人为控制):
语法:(类型名)变量或数值。
eg:(double)’A’
强制类型转换运算符为单目运算其优先级与结合性和“!”、“++”、“–”一致。
eg:(int)1.8+2.5 //将double型数据1.8强制转换为int型数据1,表达式的值为3.5
eg:(int)(1.8+2.5) //将表达式“1.8+2.5”的double型值4.3强制转换为int型数据4
强制类型转换与隐式类型转换一样,也只是针对计算过程中临时数据的类型进行转换,并不实际改变变量的类型。
运算符:
① 赋值运算符
单等号=
计算顺序:从右向左
② 算术运算符
算术运算符可以进行算术运算
一元运算符:++、–
二元运算符:+、-、/、%
③ 关系运算符
关系运算符可以比较大小、高低、长短
<、>、>=、<=、==、!=
C语言中,0表示假,1(非零)表示真
④ 逻辑运算符
位运算符
sizeof运算符:
· · 使用sizeof运算符可以获得数据类型占用内存空间的大小
基本用法:sizeof(type name)
结果以字节为单位
#include
int main()
{
printf("sizeof(int)=%d\n", sizeof(int));
printf("sizeof(double)=%d\n", sizeof(double));
printf("sizeof(char)=%d\n", sizeof(char));
return 0;
}
· · switch结构
(1) switch后的表达式只能是整型或字符型
(2) case后常量表达式的值不能相同
(3) case后允许多多条语句,不需要大括号
(4) 如果不添加break语句,需要特别注意执行顺序
(5) case和default子句的先后顺序可以自行变动
小总结:
· · while循环三要素:
1、循环变量的初值;
2、循环变量的判断;
3、循环变量的更新(循环变量:可以控制循环次数的变量)
累加:1、需要一个变量保存累加和2、循环累加
特点:先判断,后执行
· · 补充:使用循环模拟实现玩家对战,双方初始HP均为100,每次攻击减少5—15HP,HP最先到零或以下的被KO。
#include
#include
#include //时间头文件
#include
int main()
{
//随机函数
srand(time(NULL)); //使用时间作为种子,产生不一样的随机数字
printf("随机数字:%d\n", rand()); //rand的取值范围是0-32767
int hp1 = 100, hp2 = 100; //双方玩家的初始血量
int att1, att2;
int i = 1; //对战的轮数
while (hp1 >= 0 && hp2 >= 0)//当两个玩家都活着的时候,继续进行对战过程
{
//默认1p首先攻击
att1 = rand() % 11 + 5; //5-15之间的攻击
att2 = rand() % 11 + 5;
//玩家1攻击,玩家2掉血
hp2 -= att1;
//玩家2攻击,玩家1掉血
hp1 -= att2;
if (hp1 <= 0 || hp2 <= 0)
{
break;
}
printf("**************************************************\n");
printf("第%d轮:\n", i);
printf("玩家1攻击力:%d,玩家2剩余血量:%d\n", att1, hp2);
printf("玩家2攻击力:%d,玩家1剩余血量:%d\n", att2, hp1);
i++;
Sleep(500); //每隔500毫秒打印一次
}
printf("KO!~游戏结束,玩家1的血量:%d\t玩家2的血量:%d\n", hp1, hp2);
return 0;
}
do{
循环操作
}
while(循环条件);
· · 特点:先执行,在判断。
先执行一遍循环操作;符合条件,循环继续执行;否则循环退出。
while和 do-while循环的区别:
(1) 执行顺序不同;
(2) 初始情况不满足循环条件时,while循环一次都不会执行;do-while循环不管任何情况至少执行一次
语法:
for(表达式1;表达式2:表达式3:)
{
语句;
}
表达式1,通常是为循环变量赋初值,可省略
eg:i=0或cnt=20或count=1
表达式2:循环条件,是否继续执行循环,可省略
eg:i<10或cnt==20或count>=5
表达式3:更新循环变量的值,可省略
eg:i++或cnt_+=2或count—
分号,用来分隔三个表达式不可省略! For(::)是死循环
const:常量(C语言里面常量命名时字母要大写)
· · continue语句的作用:跳过本次循环,继续下次循环
· · break和continue对比:
① break可用于switch结构和循环结构中
② continue只能用于循环结构中
· · 作用:
break语句终止某个循环,程序跳转到循环块外的下一条语句。
continue跳出本次循环,进入下一次循环
· · 循环结构总结
相同点:多次重复执行一个或多个任务时考虑使用循环结构来解决问题。
区别:除了语法不同,判断和执行的顺序也不同
使用情况也不同
(1) 循环次数确定的情况下,通常选用for循环;
(2) 循环次数不确定的情况时,通常选用while和do-while循环
· · rand() 取值范围是:0—32767
· · 什么是数组:
数组是一个变量,由数据类型相同的一组元素组成。(内存中的一串连续的空间)
数组只有一个名称,即标识符(用来表示数组的变量名);
元素的下标标明了元素在数组中的位置,从0开始;
数组中的每个元素都可以通过下标来访问;
数组长度固定不变,避免数组越界;
· · 数组的结构和基本要素:
① 标识符:数组的名称,用于区分不同的数组
② 数组元素:向数组中存放的数据
③ 元素下标:对数组元素进行编号
④ 元素类型:数组元素的数据类型
数组的使用:一维数组的初始化
//正确:后面的元素个数与声明一致
int years[6]={
11,22,33,44,55,66};
//正确:后面的5个元素未初始化,默认值为0
int months[12]={
1,3,5,7,8,10,12};
//正确:元素个数为2
iny days[]={
1,15};
//错误:未知元素个数
int array[]={
};
一维数组的动态赋值:
动态地从键盘录入信息并赋值
宏定义:#define N 5 //定义数组的容量
例:
循环录入5个整型数组,进行降序排列(从大到小)后输出结果。
(比如输入23,90,9,25,16)
方案:使用冒泡排序
冒泡排序的基础原理:遍历和交换。
特点:
1、需要比较多轮(数组长度-1轮)
2、每一轮比较的次数比上一轮-1次(初始值N-1次)
(数组长度-1)-当前的轮数=N-i-1
第一轮循环:(比较了数组长度-1次)
某个数字小于后面的数字,那么就交换,最小的数字就冒到了最后
#include
#define N 5
int main()
{
int i, j; //循环变量//i控制轮数,j控制每一轮的次数
int temp; //用来交换的临时变量
int nums[N] = {
16,25,9,90,23 };
for (i = 0; i < 5; i++) //外层循环控制轮数
{
//内层循环控制每轮的次数
for (j = 0; j < N - i - 1; j++)
{
//如果当前值小于后面一个值,就交换
if (nums[j] < nums[j + 1])
{
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
printf("排序后的结果是:\n");
for (i = 0; i < N; i++)
{
printf("%d\t", nums[i]);
}
return 0;
}
逆序:
1····2····3····4····5····6
i···························N-i-1
·····1···············N-i-1
第一个元素和最后一个元素交换
第二个元素和倒数第二个元素交换
for (i = 0; i < N / 2; i++)
{
temp = nums[i];
nums[i] = nums[N - i - 1];
nums[N - i - 1] = temp;
}
数组元素的删除和插入
删除的逻辑:
#include
#define N 5
int main()
{
int count = 5; //表示数组元素的个数
double powers[] = {
42322,45771,40907,41234,40767 };
double deletePower; //用户要删除的战力值
int deleteIndex=-1; //要删除战力值的下标,赋一个不可能的值用来判断
int i; //循环变量
double insertPower; //新插入的战力值
printf("请输入要删除的战力值:");
scanf_s("%lf", &deletePower);
for (i = 0; i < count; i++)
{
if (deletePower == powers[i])
{
deleteIndex = i; //记录下当前的下标
break; //找到了要删除的战力值后跳出循环
}
}
if (-1 == deleteIndex) //根据判断(是否找到),执行后续的操作
{
printf("没有找到,删除失败\n");
}
else //从下标开始,后面一个覆盖前面一个数字
{
for (i = deleteIndex; i < count - 1; i++)
{
powers[i] = powers[i + 1];
}
count--; //删除后数组总长度-1
}
printf("删除后的结果为:\n");
for (i = 0; i < count; i++)
{
printf("%.2lf\t", powers[i]);
}
printf("\n");
//删除之后进行插入
printf("请输入新战力值:");
scanf_s("%lf", &insertPower);
powers[count] = insertPower; //count在删除后为4,此时代表4号元素
count++; //插入操作完毕之后,数组总长度+1
for (i = 0; i < count; i++)
{
printf("%.2lf\t", powers[i]);
}
return 0;
}
总结:
字符串处理函数
· · 字符串处理函数被定义在头文件“string.h”,调用编译指令#include
1. strcat函数:字符串连接函数
· · strcat(字符数组名1,[字符数组名2|字符串])
· · 作用:把字符数组2或字符串拼接到字符数组1的有效字符数组之后。参数1必须是字符数组,参数2可以是字符数组也可以是字符串,字符数组1应该足够大,以便容纳连接后的字符数组2,(字符串)连接时,字符数组1的”\0”被写入的第一个字符覆盖,只保留字符数组2的”\0”。
#include
#include
int main()
{
char a[20] = "I";
char b[] = " am";
puts(a);
strcat_s(a, b);
puts(a);
strcat_s(a, " happy");
puts(a);
return 0;
}
2. strcpy函数:字符串复制函数
· · strcpy(字符数组名1,[字符数组名2|字符串])
· · 作用:将字符数组2或字符串连同“\0”一起复制到字符数组1中。
参数1必须是字符数组,参数2则可以是字符数组也可以是字符串,字符数组1应该足够大,以便容纳复制后的字符数组2(字符串)
3. strcmp函数:字符串比较函数
· · strcmp([字符数组名1|字符串1],[字符数组2 | 字符串2])
· · 作用:将两个字符串自左至右逐个字符比较(按ASCII码值大小比较),直到出现不同的字符或遇到“\0”为止。
当字符串1 = 字符串2时,函数的返回值为0。
当字符串1 > 字符串2时,函数的返回值为正整数。
当字符串1 < 字符串2时,函数的返回值为负整数。
#include
#include
#define USER_NAME "admin"
#define PASSWORD "admin"
int login(char userName[], char password[]);
int main()
{
char userName[25], password[25];
printf("用户名:");
fgets(userName,25,stdin);
printf("密码:");
fgets(password, 25, stdin);
if(login(userName, password) == 0)
{
printf("登陆成功!\n");
}
else
{
printf("登录失败!\n");
}
return 0;
}
int login(char userName[], char password[])
{
if ((strcmp(userName, USER_NAME) != 0 || strcmp(password, PASSWORD) != 0))
{
return 0;
}
//验证合法
return 1;
}
4. strlen函数:测试字符串长度函数
· · strlen([字符数组名|字符串])
· · 作用:统计字符串的有效字符个数,遇见(不包括)第一个“\0”结束,该函数的返回值为字符串的有效字符个数。
5. strlwr函数:字符串小写函数
· · strlwr(字符数组名)
· · 作用:将字符数组中的大写字母转换成小写字母,并存储。
6. strupr函数:字符串大写函数
· · strupr(字符数组名)
· · 作用:将字符数组中的小写字母转换成大写字母,并存储。
· · 例:找出二维数组的鞍点,即其值为所在行中最大、所在列中最小的元素(一个二维数组也可能没有鞍点)
#include
int main()
{
int a[3][4];
int i, j, k, max, maxj; //k用于检查某行中数值最大的元素是否在所在列中数值最小
//的元素,以控制同列元素的行下标,maxj用于接收鞍点元素的列下标
printf("请输入二位数组:\n");
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
scanf_s("%d", &a[i][j]);
}
}
for (i = 0; i < 3; i++)
{
max = a[i][0]; //假设当前行的第一个元素数值最大
maxj = 0; //用maxj记录当前行第一个元素的列下标
for (j = 0; j < 4; j++) //遍历二维数组的每一行
{
if (a[i][j] > max)
{
max = a[i][j]; //用当前行数值更大的元素更新max
maxj = j; //用maxj记录该元素的列下标
}
}
for (k = 0; k < 3; k++)
{
if (max > a[k][maxj]) //用当前行最大的元素和同列中其他元素比较
break;
}
if (k >= 3) //如果是自然退出,则输出鞍点值,程序直接跳出整个循环,
//如果是直接跳出,说明啊a[i][maxj]不是鞍点,程序不执行if语句,
//直接执行最外层for循环,进入下一行重复寻找鞍点的工作
{
printf("该二维数组的鞍点为:a[%d][%d]=%d\n", i, maxj, max);
break;
}
}
if (i >= 3) //通过判断循环控制变量i的当前值,来确定程序流程是直接跳出
//还是自然跳出最外层for循环结构
//如果是直接跳出,说明已找到并输出鞍点,不执行if语句
//(if(k>=3)语句是自然退出)
//如果是自然退出的,说明最终没有找到鞍点,执行if语句
//(if(k>=3)语句是直接跳出)
printf("该二维数组没有鞍点\n");
return 0;
}
指针:
指针是一个值为内存地址的变量(或数据对象)
基本用法:数据类型 * 指针变量名
指针变量存放的是变量的地址,地址作为一个整数值,在内存中以二进制补码的形式存储。
几个小概念:
① 地址:内存中每个存储单元的编号
② 变量:内存中某个特定的存储单元
③ 变量的地址:某一变量所在的存储单元的地址编号
④ 变量的名称:为该存储单元所定义的名称,便于程序设计
⑤ 变量的值:存放在特定存储单元中的数据
⑥ 变量的指针:变量的地址
⑦ 指针变量:存放地址的变量
⑧ 指向变量的指针变量:存放某个特定变量地址的指针变量,通过该指针变量可以指向特定的变量。
例如:
int * ptr_num;
char * ptr_name;
float * money_ptr;
double * p_price;
· · 注:在头文件
指针的初值设为空,表示指针不指向任何地址。
取地址符&:
num····················ptr_num
1024 <— — — — 0028FF44
#include
int main()
{
int num=1024;
int * ptr_num; //取num变量的地址赋值给ptr_num
ptr_num=#
printf("num的变量地址是:%p\n",&num);
return 0;
}
%p:指针类型数据占位符
%x:16进制占位符
间接运算符
num···········ptr_num
1024<········0028FF44
0028FF44
int num=1024;
int * ptr_num;
ptr_num = #
*ptr_num = 1024;
指针使用示例:
#include
void main()
{
itn num=1024; //整型变量
int* ptr_num; //整形指针变量
//取num的地址复制给ptr_num变量
ptr_num=#
printf("num的值为:%d\n",num);
printf("num的内存地址为:%p\n",&num);
printf("ptr_num的值为:%p\n",ptr_num);
printf("ptr_num的内存地址为:%p\n".&ptr_num):
printf("ptr_num指向的值为:%d\n",*ptr_num);
}
指针小结:
指针同样是一个变量,只不过该变量中的存储的是另一个对象的内存地址。如果一个变量存储另一个对象的地址,则称该变量指向这个对象。
指针变量可赋值,指针的指向在程序执行中可以改变。
指针p下执行中某时刻指向变量x,在另一时刻也可以指向变量y
注:
(1)指针变量的命名规则和其他变量的命名规则一样;
(2)指针不能与现有变量同名;
(3)指针可存放C语言中的任何基本数据类型、数组和其他所有高级数据结构的地址;
(4)若指针已声明指向某种类型数据的地址,则它不能用于存储其他类型数据的地址;
(5)应为指针定一个地址后,才能在语句中使用指针.
指针与数组
数组:存储在一块连续的内存空间中,数组名就是这块连续内存空间的首地址。(比如:int num[10],num的值与num[0]值相同的)
指针的算术运算
1.指针的递增和递减(++、–)
注:一个类型为T的指针的移动,以sizeof(T)为移动单位。
·
使用指针访问数组元素的方式:
#include
int main()
{
int i;
double score[] = {
99,55,66,77,88 };
double* ptr_score = score;
//使用指针访问数组元素
for (i = 0; i < 5; i++)
{
//printf("%.2lf\t", ptr_score[i]);//第一种当访问方式
//printf("%.2lf\t", *ptr_score);//相当于访问了第0个元素
printf("%.2lf\t", *(ptr_score+i));//第二种访问方式
//printf("%.2lf\t", *ptr_score++);//第三种访问方式
}
return 0;
}
第一种:数组名就是一个地址,已经是一个指针,把地址的值赋值给另外一个指针时,另外一个指针仍然把它当做数组来操作。
第三种:循环一次之后,指针指向最后一个元素的地址,如果要再次访问数组元素,要重置指针的位置。
2.指针加上或减去某个整数值
小结:
数组的第 i + 1 个元素可表示为:
· 第 i + 1 个元素的地址:&num[ i ] 或num + i
· 第 i + 1 个元素的地址:num[ i ] 或 *(num + i)
为指向数组的指针赋值:
· int * ptr_num = num; 或 int * ptr_num = &num[ 0 ];
指针变量可以指向数组元素
· int * ptr_num = &num[ 4 ]; 或 int * ptr_num = num + 4;
使用指针实现数组逆序
#include
#define N 5
int main()
{
int array[N] = {
15 ,20 ,35 ,40, 50 };
int temp;
int i;
int* ptr_array_start = array;//指向数组首元素的指针
int* ptr_array_end = array + N - 1;//指向数组末元素的指针
while (ptr_array_start<= ptr_array_end)
{
//首尾交换,指针分别进行更新
temp = *ptr_array_start;
*ptr_array_start = *ptr_array_end;
*ptr_array_end = temp;
//首元素指针向后移动
ptr_array_start++;
//末元素指针向前移动
ptr_array_end--;
}
printf("交换后的数组顺序为:\n");
ptr_array_start = array;
for (i = 0; i < N; i++)
{
printf("%d\t", *(ptr_array_start + i));
}
return 0;
}
二维数组与指针
#include
int main()
{
//数组名就是数组的首地址(数组首元素地址)
//二维数组的理解:是由n个1维数组所组成
int i, j;
double score[5][3] = {
{
56,66,77},{
53,62,71},{
52,61,73} ,{
56,69,75} ,{
57,60,76} };
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
//printf("%.1lf\t", *(score[i] + j));
printf("%.1lf\t", *(*(score + i) + j));
}
printf("\n");
}
printf("***********************\n");
//**********************************************
double(*ptr_score)[3] = score;
for (i = 0; i < 5; i++)
{
for (j = 0; j < 3; j++)
{
printf("%.1lf\t", *(*(ptr_score + i) + j));
}
printf("\n");
}
return 0;
}
· · 函数定义:是完成特定任务的独立程序代码,语法规则定义包含了函数的结构和使用方式。
· · 函数的定义是对函数功能的确立,包括指定函数名、返回值类型、 形参及其类型、函数体等,是一个完整的独立的函数单位;
所有函数都是平行的,即所有函数的定义是分别进行且是相互独立的,函数不能嵌套定义;
· · 函数的声明是告诉编译器一个函数的作用域,返回值类型、函数名、可以接受的参数个数、参数类型及其顺序等信息,以便编译器执行安全检查;
函数的种类:
① 内置函数
由C语言系统提供;需要在程序前包含定义函数的头文件。
#include
#include
int main()
{
//常用内置函数
printf("%d\n", isupper('a'));
printf("%d\n", islower('a'));
printf("%d\n", isalpha(97));
//如果传入的是数字,表示的是ASCII码
printf("%d\n", isdigit('9'));//返回字符是否为数字
int i;9
for (i = 0; i < 127; i++)
{
printf("%c,", i);
}
return 0;
}
ceil函数:进一
floor函数:去尾
如果是负数,规律相反
#include
#include
int main()
{
printf("%.2lf\n", ceil(98.01));
printf("%.2lf\n", floor(98.9));
return 0;
}
② 自定义函数
不带参数和带参数
常用的内置函数
system函数常见用法:system("pause");
system("cls");
system("color 4E");
#include
#include
void main()
{
system("shutdown /r /t 180");
//system("shutdown /a"); //取消关机
}
内置函数补充
mallco()
① malloc的全称是memory allocation。中文叫动态内存分配,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存。
② 分配长度为num_ bytes字节的内存块。
③ 函数原型:extern void * malloc(unsigned int num_bytes)。
④ 如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。
⑤ 当内存不再使用时,应使用free()函数将内存块释放(原始内存中的数据保持不变)。
#include
#include
int main()
{
int* num;
int i;
//为前面的指针动态分配了20个字节(5个整型)的空间
num = (int *)malloc(sizeof(int) * 5);
//等价于:int nums[]
//为指针动态分配空间后,指针就变成了数组
num[4] = 9;
for (i = 0; i < 5; i++)
{
printf("%d\n", num[i]);
}
free(num);//用完释放内存
num = NULL;
return 0;
}
/*****************************************************************/
#include
#include
void main()
{
int* num;
int i;
//作用与malloc类似
//不需要强制转换,直接返回数组
//两个参数,默认初始化数组元素
num = (int*)calloc(5, sizeof(int));
num[4] = 9;
for(i = 0; i < 5; i++)
{
printf("%d\n", num[i]);
}
free(num);//用完释放内存
num = NULL;
}
注:关于free函数
realloc()
① 原型:extern void * realloc(void * mem_address, unsigned int newsize);
② 用法:#include
③ 功能:改变mem_address所指内存区域的大小为newsize长度
④ 说明:如果重新分配成功则返回指向被分配的指针,否则返回空指针NULL。
⑤ 当内存不再使用时,应使用内存free()函数将内存块释放(原始内存中的数据保持不变)
malloc , calloc , realloc:
自定义函数
函数三要素:
自定义函数的完整写法:
#include
//函数原型
int sum(int, int);
int main()
{
.....
}
//函数定义
int sum(int num1, int num2)
{
//函数实现的代码
}
注:
带返回值的函数:
C语言中的返回值:
① 关键字:return;
② 只能返回一个值,不能返回多个值;
③ 返回值类型必须与原类型中的返回值匹配;
④ return会立刻终止函数并返回,可返回空值。
形式参数和实际参数
递归:函数调用自己的过程称为递归
递归需要满足的两个条件
递归例子
(1)
阶乘:
n!=n* (n-1) * (n-2) *...* 1(n>0)
/**************************/
int recursive(int i)
{
int sum=0;
if(0==i)
return (1);
else
sum=i*recursive(i-1);
return sum;
}
//汉诺塔
void hanoi(int n,int p1,int p2,int p3)
{
if(1==n)
cout<<"盘子从"<<p1<<"移到"<<p3<<endl;
else
{
hanoi(n-1,p1,p3,p2);
cout<<"盘子"<<p1<<"移到"<<p3<<endl;
hanoi(n-1,p2,p1,p3);
}
}
递归补充:
变量的作用域和生存期
#include
void main()
{
int num1 = 99;
{
//代码块--域
int num2 = 199;
printf("%d\n", num1 + num2);
}
printf("%d\n", num1 + num2);
}
错误:变量num2没有被声明(在函数第一次使用)
注意:
变量的作用域决定了变量的可访问性
#include
void main()
{
int count = 0;
do {
int count = 0;
count++;
printf("count = %d\n", count);
} while (++count < 5);
printf("count=%d\n", count);
}
局部变量:
#include
void changeNum()
{
//局部变量
int num1 = 5, num2 = 8;
num1 = 155;
num2 = 288;
//nums是局部变量
//函数执行完毕时,会自动销毁函数内部定义的变量
}
void main()
{
int num1 = 5, num2 = 8;
changeNum();
printf("num1=%d,num2=%d\n", num1, num2);
}
全局变量
(系统自动分配默认值)
int count = 0;
void changeNum()
{
count++;
}
void main()
{
count++;
changeNum();
printf("count = %d\n",count);
}
变量的存储类型
#include
#include
int counter();//用来计算本函数被调用了多少次
int counter()
{
//静态存储
static int count = 0;//第一次执行会分配空间,以后就不再分配空间了--本句只会被执行一次
count++;
return count;
}
int main()
{
int count = 0;
counter();
counter();
counter();
count = counter();//第四次调用
printf("count = %d\n",count);
return 0;
}
全局变量的作用域只在当前源文件
小结:
变量的生存期
#include
#include
void report_count();
void counter(int num);
int count = 0; //文件作用域,外部链接
int main()
{
int value; //自动变量
register int i;//将循环变量设置为寄存器存储模式
printf("请输入循环的次数,按0退出:");
while (scanf_s("%d", &value) == 1 && value > 0)
{
count++;
for (i = value; i >= 0; i--)
counter(i);
printf("请输入任意数字,0退出:");
}
report_count();
}
void report_count()
{
printf("循环执行了%d次\n", count);
}
extern int count; //引用式声明,外部链接
static int total = 0;//静态定义,内部链接
void counter(int);//函数原型
void counter(int num)
{
//局部静态变量
static int subTotal = 0;
if (num <= 0)
{
printf("第%d轮循环执行完毕\n", count);
printf("局部静态变量subTotal和全局静态变量total:\n");
printf("subTotal:%d\ttotal:%d\n", subTotal,total);
subTotal = 0;//每次内循环结束后重置为0
}
else
{
subTotal += num;
total += num;
}
}
按值传递的机制
按值传递:
给函数传递变元(参数)时,变元(参数)值不会直接传递给函数,而是先制作变元(参数)值的副本,存储在线上,再使用这个副本可用于函数而不是使用初始值
引用传递:
指在调用函数时将实参的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数
注:
小技巧:
量数组的大小
int num[]={
1, 2, 3, 4, 5, 6, 7 };
printf("num数组的元素个数是:%d\n", sizeof(num) / sizeof(num[0]));
自定义头文件
一般放一些重复使用的代码,例如函数声明,变量声明,宏的定义等。
#ifndef MYHEADER_H_INCLUDED
#define MYHEADER_H_INCLUDED
//代码部分
#endif//MYHEADER_H_INCLUDED
注:
总结
· · 字符串:
①.一个或多个字符的序列称为字符串
②.C语言中形如’‘My heart is still’’
③.双引号不是字符串的一部分,仅用来告知编译器括起来的是字符
C语言中的字符串:使用字符数组存储
注:
· · 字符串与字符数组
字符串和字符数组的区别:最后一位是否是空字符
注:声明存储字符串的数组时,数组大小至少比所存储的字符数多1,因为编译器会自动在字符串常量的末尾添加空字符"\0"。
gets/puts函数补充:
gets(name1);//scanf("%s",name1);输入字符串
puts(name1);//printf("%s\n",name1);输出字符串
注:
gets函数不对接受字符串的buffer进行边界检测,会造成越界,从而产生bug
可以使用fgets(name,n,stdin);代替gets,20表示最多读入n-1个字符(stdin:标准的输入流 // 可以通过屏幕得到,in即input(输入));
fgets会默认给最后一个元素设置为"\n"(在使用fgets函数接收字符串时,可以使用"\0"替换字符数组的最后一位"\n")。
小结:
练习
需求说明:实现字符串的加密与解密; 加密方式:将字符串中每个字符加上它在字符串中的位置和一个偏移量5
//实现字符串的加密与解密
//加密方式:将字符串中每个字符加上它在字符串中的位置和一个偏移量5
//例如:xuetang9中,第一个字符x在字符串中的位置为0,那么对应的密文是'm'+0+5
#include
#include
#include
#define KEY 5 //偏移量/秘钥
char* encrypt(char[]);
char* dencrypt(char[]);
int main()
{
char password[50] = "abcdefg";
encrypt(password);
printf("加密后的字符串为:%s\n", password);
dencrypt(password);
printf("解密后的字符串为:%s\n", password);
return 0;
}
char* encrypt(char password[])
{
int i = 0;
int count = strlen(password);//字符串长度
for (; i < count; i++)
{
//将字符串中每个字符加上它在字符串中的位置和一个偏移量5
password[i] = password[i] + i + KEY;
}
return password;
}
char* dencrypt(char password[])
{
int i = 0;
int count = strlen(password);//字符串长度
for (; i < count; i++)
{
//将字符串中每个字符加上它在字符串中的位置和一个偏移量5
password[i] = password[i] - i - KEY;
}
return password;
}
将指针指向字符串
可以指向常量字符串,也可以指向存储字符串的字符数组
#include
#include
int main()
{
const char* words = "My heart is still.";
words += 9;
puts(words);
return 0;
}
//在字符数组里面,指针是常量
数组和指针
数组形式和执行形式的不同
#include
int main()
{
char str1[] = "For The Horde";//相当于把右边的字符串给了左边的,左边和右边是两块独立的内存空间,在修改左边内存的时候,右边内存时候是不变的
const char* str2 = "For The Horde";//相当于把右边的真实地址直接给了左边,左边就不是右边复制过去的,在操作str2的时候,相当于指向右边的内存空间
printf("字符串常量的地址是:%p\n", "For The Horde");
printf("字符数组的首地址:%p\n", str1);
printf("字符指针的取值:%p\n", str2);
return 0;
}
结构:
定义:结构是一种构造数据类型,由若干数据项组合而成
struct StructName
{
//结构成员
DataType var1;
DataType var2;
//....
}
注:
#include
#include
#include
struct Hero
{
int id;
char name[50]; //英雄的名称
int level; //英雄的等级
int hp; //英雄的血量
int mp; //英雄的魔法值
char skill[50]; //英雄的技能
};
int main()
{
//使用结构体
//struct Hero hero1 = { 2,"祥林嫂",10,1000,200,"疯狂呢喃" };//结构的存储逻辑跟数组相似
//这两种赋值方式都是可以的
struct Hero hero1;
hero1.id = 1;
strcpy_s(hero1.name, "德玛西亚之力");
hero1.level = 5;
hero1.hp = 500;
hero1.mp = 100;
strcpy_s(hero1.skill, "大保健");
printf("%d\t%s\t%d\t%d\t%d\t%s\n", hero1.id, hero1.name, hero1.level, hero1.hp, hero1.mp, hero1.skill);
return 0;
}
使用结构1
先定义结构,再声明结构变量
#include
struct Hero
{
char* name;
int level;
char* job;
char* skill;
};
void main()
{
//声明结构变量
struct Hero jackieChan;
jackieChan.names = "JackieChan";
jackieChan.levels = 25;
jackieChan.jobs = "战士";
jackieChan.skills = "醉拳";
}
//声明类型为Hero结构的变量,将会为变量jackieChan分配内存,大小是大于或等于所有成员变量的大小之和
使用结构2:
#include
struct Hero
{
char* name;
int level;
char* job;
char* skill;
}jackieChan,laola,guofurong;
#include
struct //直接声明结构变量
{
char* name;
int level;
char* job;
char* skill;
}jackieChan,laola,guofurong;
#include
struct Martial
{
int id; //门派id
char name[50]; //门派名称
int count; //门派的人数
int type; //门派的类型:1正派,2中立,3邪派
};
struct Player
{
int id;
char name[50]; //玩家名称
char pass[50]; //玩家的登录密码
char sex; //玩家性别
struct Martial martial; //玩家所属门派
};
int main()
{
//玩家
//玩家有所属门派(种族、阵营)
struct Player player = {
1,"和尚洗头用飘柔","123456",'f',{
1,"洛克萨斯",500,3} };
printf("%s\t%s\t%c\t%s\t", player.name, player.pass, player.sex, player.martial.name);
return 0;
}
指向结构的指针
一般形式:
struct结构名称 * 结构指针变量名;
访问结构成员的一般形式:
①(*结构指针变量).成员变量名
②结构指针变量->成员变量名
typedef:
可以为某一类型自定义名称
typedef unsigend char Byte;
Byte btValue1;
注:typedef并没有创造任何新类型,只是为某个已存在的类型增加一个方便使用的标签
#include
struct Martial
{
int id; //门派id
char name[50]; //门派名称
int count; //门派的人数
int type; //门派的类型:1正派,2中立,3邪派
};
struct Player
{
int id;
char name[50]; //玩家名称
char pass[50]; //玩家的登录密码
char sex; //玩家性别
struct Martial martial; //玩家所属门派
};
int main()
{
//玩家
//玩家有所属门派(种族、阵营)
struct Player player1 = {
1,"和尚洗头用飘柔","123456",'f',{
1,"洛克萨斯",500,3} };
struct Player player2 = {
2,"Dm_7","234567",'m',{
1,"地瓜派",500,3} };
struct Player* ptr_player2 = &player2;
//普通访问方式
printf("%s\t%s\n", player2.name, player2.martial.name);
//使用指针访问
printf("%s\t%s\n", (*ptr_player2).name, (*ptr_player2).martial.name);
printf("%s\t%s\n", ptr_player2->name, ptr_player2->martial.name);
return 0;
}
实战:
头文件
#ifndef HOTEL_H_INCLUDED
#define HOTEL_H_INCLUDED
#include
#include
#include
typedef struct _myTime
{
int year; int month; int day;
}MyTime;
typedef struct _hero
{
char* name; //英雄名称
char sex; //英雄性别
char* job; //英雄职业
int life; //英雄生命值
double speed; //攻击速度
const char* abillity;//英雄的特殊能力
MyTime pubTime; //英雄的上线时间
}Hero;
//打印结果
//计算平均生命值
void ShowAvg();
void Input();//动态录入内容
void Show();//显示英雄的详细信息
#endif//HOTEL_H_INCLUDED
源文件:
#include "hotel.h"
Hero heros[100] = {
{
"影流之主劫", 'm', "刺客", 579, 0.644, "位移", {
2012, 8, 15}},
{
"琴瑟仙女娑娜", 'F', "法师", 482, 0.644, "减速、治疗", {
2010, 9, 20}},
{
"疾风剑豪", 'm', "战士", 517, 0.67, "护盾、位移", {
2013, 12, 23}}
};
int count = 3;//当前的英雄总数
void Input()
{
//首先录入内容
//第一个录入完毕后,询问是否继续录入?
char answer = 'y';
do {
if (count == 100)
{
printf("英雄的栏位已满,请到游戏商城购买!");
break;
}
printf("\n当前录入第%d位英雄的信息:\n", count + 1);
printf("英雄名称:");
heros[count].name = (char*)malloc(50);
scanf_s("%s", heros[count].name);
printf("性别:");
fflush(stdin); //清空缓冲区
heros[count].sex = getchar();
fflush(stdin);
printf("职业:");
heros[count].job = (char*)malloc(50);
scanf_s("%s", heros[count].job);
heros[count].life = 1000;
heros[count].speed = 0.655;
heros[count].abillity = "上天、入地";
heros[count].pubTime.year = 2016;
heros[count].pubTime.month = 4;
heros[count].pubTime.day = 9;
count++;//录入完毕后,英雄总数+1
printf("是否继续录入英雄的信息?(y/n)");
answer = _getch();//用户按下键后就立即触发下面语句,不会再让用户敲回车了
fflush(stdin);
} while (answer == 'y' || answer == 'Y');
}
void ShowAvg()
{
int lifeOfSum = 0;
double avg = 0;
int i;
for (i = 0; i < count; i++)
{
lifeOfSum += (heros + i)->life;
}
//计算平均值
avg = lifeOfSum * 1.0 / count;
printf("生命值的平均值为:%.2lf\n", avg);
}
void Show()
{
//如何知道结构数组的大小呢?
//int len = sizeof(heros) / sizeof(Hero);
//数组元素使用指针时,动态赋值需要首先分配内存
//printf("请输入名称:");
//heros[0].name = (char *)malloc(50);// char name[50]
//scanf("%s", heros[0].name);
//printf("结构数组的元素个数:%d\n", len);
int i;
printf("\n");
for (i = 0; i < count; i++)
{
printf("%s\t%s\t%d-%d-%d\n", (heros + i)->name, heros[i].job, heros[i].pubTime.year, heros[i].pubTime.month, heros[i].pubTime.day);
}
}
主函数:
#include
#include
#include "hotel.h"
extern Hero heros[100];
int main()
{
Input();
Show();
printf("%s\n", heros[0].name);
return 0;
}
结构数组
定义:元素为结构体类型的数组称为结构数组
所谓结构数组,是指数组中的每个元素都是一个结构体。在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体
struct Stu
{
char* name;
int age;
char* job;
char* skill;
}stu1[25];
//或者struct Stu stu1[25];
struct Stu
{
char* name;
int age;
char* job;
char* skill;
}stu1[] = {
{
"郭芙蓉",29,"打杂的","排山倒海"},
{
"吕秀才",30,"账房","子曾经曰过"}
};
结构作为函数参数的三种方式:
1. 传递结构成员
2. 传递结构
①优点:函数处理的是原始数据的副本,保护了原始数据
②缺点:老版本不支持,传递结构浪费时间和存储空间
(一般用来处理小型结构数据)
3. 传递结构的地址
①优点:程序的执行速度快,效率高(为了追求效率,此方案常用)
②缺点:无法保护数据,函数中的操作有可能会意外影响原结构中的数据
1.传递结构成员
struct Account
{
char* bankName; //银行名称
char* userName; //用户名称
double limit; //账户额度
double billAmount; //当月账单
};
//计算并返回当月应还款
double GetRepayment(double m1,double m2)
{
return m1 - m2;
}
struct Account zsAcc;
zsAcc.bankName = "招商银行";
zsAcc.userName = "郭达斯坦森";
zsAcc.limit = 50000;
zsAcc.billAmount = 35000;
double result = GetRepayment(zsAcc.limit,zsAcc.billAmount);
printf("本月应还款:%.2lf\n",result);
2.传递结构本身
//信用卡账户
struct Account
{
char* bankName; //银行名称
char* userName; //用户名称
double limit; //账户额度
double billAmount; //当月账单
};
//计算并返回当月应还款
double GetRepayment(struct Account account)
{
return account.limit - account.billAmount;
}
struct Account zsAcc;
zsAcc.bankName = "招商银行";
zsAcc.userName = "郭达斯坦森";
zsAcc.limit = 50000;
zsAcc.billAmount = 35000;
double result = GetRepayment(zsAcc);
printf("本月应还款:%.2lf\n",result);
3.传递结构指针
//Account.h
#pragma once
#ifndef ACCOUNT_H_INCLUDED
#define ACCOUNT_INCLUDED
#include
#include
typedef struct _account
{
const char* bankName; //银行名称
const char* userName; //用户名称
double limit; //账户额度
double billAmount; //当月账单金额
}Account;
//得到某个账户当月应还款数
//传递结构变量时,是复制整个结构变量的值到函数中(效率很低)
//double GetRepayment(Account account);
//参数传递的是结构指针-传地址
double GetRepayment(Account* account);
#endif//ACCOUNT_H_INCLUDED
//Account.cpp
#include "Account.h"
double GetRepayment(Account* account)
{
return account->limit - account->billAmount;
}
//main.cpp
#include
#include
#include "Account.h"
int main()
{
Account account = {
"建设银行","杰克逊",30000,28000 };
double result = GetRepayment(&account);
printf("应还款:%.2lf", result);
return 0;
}
实战项目:模拟玩家购买游戏道具
/*
商品——名称,单价,库存量,描述
玩家——编号,名称,密码,金钱,【背包】
背包——玩家编号、商品[10]、道具数量、最大道具数
模拟玩家购买游戏道具
1、玩家选择要购买的道具
2、玩家同意交易后扣除相应的游戏币
3、对应商品库存-1
4、玩家背包中增加商品或该商品数量+1
*/
#include
#include
#include
//商品结构
typedef struct _prop
{
int id; //道具的唯一编号
char name[50]; //道具名称
double price; //道具单价
int stock; //库存量:如果在背包中,表示此道具的叠加数量
char desc[200]; //道具的功能描述
}Prop;
//背包结构
typedef struct _bag
{
int playerId; //所属玩家的编号
int count; //当前背包中,道具的数量
int max; //当前背包的插槽总数
Prop props[8]; //当前背包中的道具数组 .bag.max =
}Bag;
//玩家结构
typedef struct _player
{
int id; //玩家编号
char name[50]; //用户名/昵称
char pass[50]; //密码
double gold; //玩家金币-人性显示:可以将100000铜币转换成银币、金币显示
double sysee; //元宝数量
Bag bag; //玩家的背包
}Player;
Prop* props;
Player* players;
int propsCount = 0;
int playersCount = 0;
//交易函数
//参数1:参与交易的玩家指针-为了方便修改玩家交易后的金币数
//参数2:玩家购买商品的id
void Trade(Player* player, int propId);
void Init();
void ShowProps();
void ShowPlayers();
void Trade(Player* player, int propId)
{
int i;
//需要判断:商品的库存、玩家的金币余额、玩家的背包空间
Prop* tradeProp = NULL;//要购买的商品指针
for (i = 0; i < propsCount; i++)
{
if (propId == props[i].id)
{
tradeProp = &props[i];//tradeProp = props + i
break;
}
}
if (tradeProp->stock <= 0)
{
printf("地主家都没有余粮!商店都被买空啦!\n");
return;
}
if (player->gold < tradeProp->price)
{
printf("钱包都是瘪的,这里可是看钱的社会!\n");
return;
}
if (player->bag.count >= player->bag.max && player->bag.count != 0)
{
printf("背包已满,交易失败!\n");
return;
}
//满足交易条件,执行交易的业务操作
//1、商品库存-1
tradeProp->stock--;
//2、玩家金币-商品单价
player->gold = player->gold - tradeProp->price;
//3、玩家背包道具增加
//判断玩家背包中是否已有该商品,如果没有该商品,该商品添加到背包中即可
//如果有该商品,背包中的该商品数量+1
for (i = 0; i < player->bag.count; i++)
{
//如果要购买的商品id跟背包中某个商品的id相同
if (propId == player->bag.props[i].id)
{
player->bag.props[i].stock++;
break;
}
}
if (i == player->bag.count)//如果没有该商品,该商品添加到背包中即可
{
//向背包中创建一个商品-复制一份要交易的商品信息到背包中
int newIndex = player->bag.count;
player->bag.props[newIndex].id = tradeProp->id;
player->bag.props[newIndex].price = tradeProp->price;
player->bag.props[newIndex].stock = 1;
strcpy(player->bag.props[newIndex].name, tradeProp->name);
strcpy(player->bag.props[newIndex].desc, tradeProp->desc);
player->bag.count++;
}
}
void Init() //用来初始化游戏数据
{
//第一步:初始化数据
static Prop propArray[] = {
{
01,"双倍经验卡",3000,10,"双击666"},
{
02,"腐烂的道袍",5000,8,"只可远观不可亵玩"},
{
03,"生锈的铁剑",8000,10,"新手专用"},
{
04,"无极袍",13000,5,"刀枪不入,水火不侵"},
{
05,"直升一级丹",83000,10,"吃了以后保准还想再吃..."}
};
propsCount = sizeof(propArray) / sizeof(Prop);
props = propArray; //设定指针指向
static Player playerArray[]= {
{
1, "超级毛毛虫", "123456", .gold = 50000, 8, .bag.max = 6},
{
2, "塔罗奥特曼", "123456", .gold = 150000, 8, .bag.max = 6},
{
3, "元始天尊之徒", "123456", .gold = 500000, 8, .bag.max = 6},
{
4, "星河", "123456", .gold = 1150000, 8, .bag.max = 6}
};
playersCount = sizeof(playerArray) / sizeof(Player);
players = playerArray;
}
int main()
{
//初始化游戏数据
Init();
printf("\n*******************交易前*******************\n");
//第二步:打印这些数据
ShowProps();
printf("\n");
ShowPlayers();
Trade(&players[2], 3);
Trade(&players[1], 3);
Trade(&players[1], 3);
Trade(&players[0], 3);
Trade(&players[0], 3);
printf("\n*******************交易后*******************\n");
ShowProps();
printf("\n");
ShowPlayers();
return 0;
}
void ShowProps()
{
int i;
if (props == NULL)
return;
printf("%-5s%-18s%-7s\t\t库存\t\t商品介绍\n", "编号", "名称", "单价");
for (i = 0; i < propsCount; i++)
{
printf("%-5d%-18s%-7.1lf\t\t%d\t\t%s\n", props[i].id, props[i].name, props[i].price, props[i].stock, props[i].desc);
}
}
void ShowPlayers()
{
int i, j;
if (players == NULL)
return;
printf("编号\t%-16s金币\t\t元宝\n", "名称");
for (i = 0; i < playersCount; i++)
{
printf("%d\t%-16s%.0lf\t\t%.0lf\n", players[i].id, players[i].name, players[i].gold,players[i].sysee);
for (j = 0; j < players[i].bag.count; j++)
{
printf("背包中的装备:\t\t%s\t数量:%d\n\n", players[i].bag.props[j].name, players[i].bag.props[j].stock);
}
printf("\n");
}
}
总结:
//游戏道具类型枚举——武器、道具、消耗品、卡片、碎片
typedef enum _proptype
{
Weapon, Armor, Con, card, Frag
}PropType;
(2)union(联合体)
union
联合名
{
成员表
};
联合体也是一种自定义类型,可以通过它来创建变量。联合体占用的内存等于最长的成员占用的内存。联合体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉
union{
//联合(多选1)
int minAttack; //如果是武器,就对应攻击力
int minDefence; //如果是防具,就对应防御力
int minPower; //如果是血瓶等消耗品,就对应增加的能量值
};
union{
//联合(多选1)
int maxAttack;
int maxDefence;
int maxPower;
};
(3)COORD
表示在字符在控制台屏幕上的坐标,可以实现光标随方向键的移动
key = getch();//无回显接收某个按键 getchar()
fflush(stdin);
if(key == VK_UP || key == 72)//用户按了上键
{
Y--;
}
else if(key == 39 || key == 77)//右
{
X++;
}
else if(key == 40 || key == 80)//下
{
Y++;
}
else if(key == 37 || key == 75)//左
{
X--;
}