导图社区 C语言基础
这是一篇关于C语言基础的思维导图,涵盖了语言基础、数据类型、运算符和表达式、C语言及流程控制结构、格式化输入/输出、数组、指针、函数、存储管理、结构、联合、枚举和位域、预处理、补充等多个模块。每个模块都详细介绍了相关概念、语法规则、使用方法以及示例代码等内容,例如在函数模块中,介绍了函数的嵌套调用、递归调用等工作过程;在指针模块中,阐述了指针的本质、使用场景等。对于C语言初学者来说,此模板能够帮助他们快速搭建起C语言的知识框架,从语言基础、数据类型、运算符和表达式等基础内容,到函数、指针、数组等重要且较难的知识点,都有清晰的呈现和细致的讲解,有助于初学者系统地学习和掌握C语言;编程爱好者可以借助模板对C语言知识进行全面复习和梳理,加深对各个知识点的理解,提升编程能力;计算机专业的学生在课程学习过程中,可将此模板作为辅助工具,更好地理解和消化课堂所学内容,为后续学习更高级的编程语言和计算机技术打下坚实基础。该思维导图模板借助万兴脑图软件绘制,无论是用于个人学习、教学讲解,还是知识总结,都能有效提升学习效率,助力学习者快速掌握C语言基础,开启编程之旅。
编辑于2026-04-18 20:57:09C语言基础
1. 语言基础
1.1. C程序构成分析
1.1.1. 头文件
1.1.2. 主函数
有且只能有一个
void main(void)
int main(void)
int main(int argc,char *argv[])
1.1.3. 其他组成变量
全局变量声明
#include <stdio.h> int i = 10; //全局变量,在函数外定义的变量叫全局变量 void print(int a)//自定义的print函数 { printf("print i=%d\n", i); } int main() { printf("main i=%d\n", i); int i = 5;//当这里加了int后,就是在main定义了一个名为i的局部变量 printf("main i=%d\n", i); print(i); return 0; }
自定义函数
变量声明
用户代码
输入输出
函数调用
返回语句
return
注释
"//" ; "/* */"
1.2. VS2019调试
VS2019的C和C++是集成在一起的 选C++就可以了 当你用 .c 那么就按c来执行 当你用.cpp 那么就按C++来执行
2. 数据类型
2.1. 数据的存储
2.1.1. 内存单元
8个二进制的位bit组成一个字节 Byte
2.1.2. 数据的存储
字符数据
ascii码
数值数据
2^8=256 2^16=65536 2^32=4294967296
2.2. 数据类型简介
2.2.1. 基本类型

整数类型int
int 2^16
字符类型
char 2^8
实型(浮点型)
单精度实型float
双精度实型double
2.2.2. 构造类型
数组类型[ ]
结构类型struct
联合类型union
枚举类型enum
2.2.3. 指针类型*
2.2.4. 空类型
void
2.3. 标识符和关键字
2.3.1. 标识符
定义规则: 标识符可以由字母、数字(0~9)和下画线“_”组成。 C标识符区分大小写,例如“num5”和“NUM5”代表两个不同的标识符。 C标识符第一个字符必须是小写字母(a~z)、大写字母(A~Z)或下画线“_”。例如“count1”、“C_1”等,都是正确的。而“5num”则是错误的标识符,在编译时系统会出现错误提示。另外,有些编译系统专用的标识符是以下画线开头,为了程序的兼容性和可移植性,建议一般不要以下画线开头来命名标识符。 C标识符定义不能使用C关键字,也不能和用户已使用的函数名或C库函数同名。例如“int”是不正确的标识符,“int”是关键字,所以它不能作为标识符。 对于标识符的长度,C89标准规定,编译器至少应该能够处理31个字符(包括31)以内的标识符。C99标准规定,编译器至少应该能够处理63个字符(包括63)以内的标识符。在程序中,如果使用超出最大数目限制的字符来命名标识符,编译器会忽略超出的那部分字符。不过,为了使用和理解方便,尽量不要使用过长的标识符。
2.3.2. 关键字

2.4. 分隔符
区分或者定界C程序中不同的数据对象
2.4.1. 单分隔符
单个ascii字符 +,- * / !| < > . , ; : 、 用“;”进行隔离语句,例如int i, j, k; 用“,”进行隔离,例如char ch1, ch2; 用“*”做指针,例如char *pter; 用“#”做预处理伪指令,例如#define PI 3.1415926; 用“^”标识特殊寄存器的位,例如sbit P10=P1^0。
2.4.2. 组合分隔符
组合分隔符是由两个或者两个以上特定字符组合而使用,例如<=、!=、>=、{、}、[、]、(、)等。典型的组合分隔符使用示例如下: 用“<=”表示比较关系,例如if(x<=10); 用“[ ]”对数组说明,例如char ch[ ]=''This is a new string!''; 用“( )”进行隔离,例如s=Fun(r); 用“{}”进行隔离复合语句,例如for {i=0;i<200;i++};
2.5. 常量
2.5.1. 直接常量
2.5.2. 符号常量
#define符号常量 常量
#define pi 3.1415 #include <stdio.h> //引用头文件 #define PI 3 //PI就是符号常量 int main() { int a=3;//a就是一个变量 a = 5; //PI = 10;//符号常量不可以赋值 printf("%d\n", PI); }
const
const <符号类型说明符> <常量名> =<常量数值>
const double pi=3.1415
2.6. 变量
2.6.1. 变量声明
int num,cout; float fset,pro_fit,lost;
2.6.2. 变量初始化
赋值初始化
int age; age=16; int a,b,c,d; a=b=c=d=12;
声明初始化
int age=18;
2.7. 整型数据
2.7.1. 整型数据声明
2.7.2. 整型数据的存储
2.7.3. 整型常量
2.7.4. 整型数据的输入
输入不同类型的整型数据
在scanf()函数中,使用%d控制符可输入带符号的十进制整数。除此之外,还可以使用以下控制符输入不同类型的整数: %ld:输入signed long数值; %hd:输入signed short数值; %lld:输入signed long long数值; %u:输入unsigned int数值; %lu:输入unsigned long数值; %llu:输入unsigned long long数值。 #include <stdio.h> #include <stdlib.h> int main() { int x; printf("请输入整形数据x=:\n"); scanf_s("%d",&x); return 0; }
输入不同进制的整型数据
%x; 输入十六进制整数 %o: 输入八进制整数 %d: 输入十进制整数
2.7.5. 整型数据的输出
输出不同类型的整形数据
输出不同进制的整型数据
#include <stdio.h> #include <stdlib.h> int main() { int x; printf("请输入十进制整型数据x=:\n"); scanf("%d",&x); printf("变量x的十进制表示为%d\n",x); printf("变量x的十六进制表示为%x\n",x); printf("变量x的八进制表示为%o\n",x); system("pause"); return 0; }
2.8. 字符型数据
2.8.1. 字符型变量的声明
#include <stdio.h> #include <stdlib.h> int main() { char c1,c2,c3,c4; //定义字符变量 c1=97; //赋值 c2=98; //赋值 c3='a'; c4='b'; printf("c1=%c\nc2=%c\nc3=%c\nc4=%c\n",c1,c2,c3,c4); //输出结果 system("pause"); return 0; } c1=a c2=b c3=a c4=b 注意:当字符型变量输入数字,输出的是数字对应的ASCII码的字符
2.8.2. 字符型的存储
一定是8位,对应ascii码
2.8.3. 字符型常量
2.8.4. 字符型大小写切换
#include <stdio.h> int main() { char c = 'a';//现在是小写字母a,要变为大写字母A c = c - 32; printf("%c\n", c);//以字符形式来输出c }
2.9. 浮点型数据
2.9.1. 浮点型数据的声明

float
6位有效数字
double
8位有效数字
2.9.2. 浮点型数据的存储

2.9.3. 浮点型常量
2.9.4. 浮点型数据的输入
#include <stdio.h> #include <stdlib.h> int main() { float a; //定义a为单精度浮点型变量 double b; //定义b为双精度浮点型变量 long double c; //定义b为长双精度浮点型变量 printf("请输入单精度浮点型变量a=\n"); scanf("%f",&a); //输入数据 printf("请输入双精度浮点型变量b=\n"); scanf("%lf",&b); //输入数据 printf("请输入长双精度浮点型变量c=\n"); scanf("%lf",&c); //输入数据 system("pause"); return 0; }
2.9.5. 浮点型数据的输出
#include <stdio.h> #include <stdlib.h> int main() { float a; //定义a为单精度浮点型变量 double b; //定义b为双精度浮点型变量 long double c; //定义b为长双精度浮点型变量 printf("请输入单精度浮点型变量a=\n"); scanf("%f",&a); //输入数据 printf("请输入双精度浮点型变量b=\n"); scanf("%lf",&b); //输入数据 printf("请输入长双精度浮点型变量c=\n"); scanf("%lf",&c); //输入数据 printf("a=%f,b=%lf,c=%lf\n",a,b,c); //输出数据 printf("a=%e,b=%le,c=%le\n",a,b,c); //输出数据 system("pause"); return 0; }
2.10. 类型转换
2.10.1. 混合运算中的类型转换
//混合运算 #include <stdio.h> int main() { int i = 5; float j = i / 2;//j输出的是2 float k = (float)i/2 ;//k输出的是2.5 printf("j=%f,k=%f\n", j,k); }
2.10.2. 强制类型转换
#include <stdio.h> #include <stdlib.h> int main() { int ch1,ch2; //声明变量 double f1,f2; ch1=67; //赋初值 f1=123.565; ch2=(int)f1; //浮点型转换为整型 f2=ch1; //整型转换为浮点型 printf("ch1=%d\t\tch2=%d\n",ch1,ch2); //输出结果 printf("f1=%f\tf2=%f\n",f1,f2); system("pause"); return 0; }
(类型) 表达式
(double) i 把变量i的值转换为double类型 (short)a+b
3. 运算符和表达式
3.1. 运算符
3.1.1. 运算符概述
算术运算符:用于进行各类算术数值运算。包括加(+)、减(-)、乘(*)、除(/)、取余(又称为取模运算,%)、自增(++)和自减(−)共7个。 关系运算符:用于进行比较运算。包括大于(>)、小于(<)、等于(==)、 大于等于(>=)、小于等于(<=)和不等于(!=)共6个。 逻辑运算符:用于进行逻辑运算。包括与(&&)、或(||)和非(!)共3个。 位运算符:参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)和右移(>>)共6个。 赋值运算符:用于进行赋值运算,分为简单赋值(=)、复合算术赋值(+=,−=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)三类共11种。 条件取值运算符:用于进行条件取值计算(?:)。 逗号运算符:用于把若干表达式组合成一个表达式(,)。 指针运算符:用于进行跟指针相关的运算,包括取内容(*)和取地址(&)共2个。 求字节数运算符:用于计算数据类型所占的字节数(sizeof)。 特殊运算符:包括括号(),下标[],成员(→,.)等几种。 另外,按照要求参与运算操作数的数目来分,C语言运算符可以分为如下几类: 单目运算符;++ -- 双目运算符;+ 三目运算符(又称多目运算符)。
3.1.2. 算术运算符
普通算术运算
普通算术运算符用法比较简单,不过在实际使用中需要注意如下几点: 除法运算(/)是将第1个操作数除以第2个操作数,并取除法结果的整数部分。 取模运算(%)是将第1个操作数除以第2个操作数,并取除法结果的余数部分。因此,取模运算又称为取余运算。这里需要格外强调的是,取模运算符不能应用于浮点型数据的操作。 减法运算符(−)除了进行减法运算外,也可以用作取负操作,如-a是取变量a的负操作。所不同的是,当用做减法时需要两个操作数参与运算,而用作取负时只需要一个操作数参与运算。 如果运算符两侧操作数的类型不同,则系统自动进行类型转换,使操作数具有相同的类型,然后再进行运算。具体可以参阅类型转换相关内容。
自增自减运算
x=++t; // t先+1,再赋值给x x=t++; //t先赋值给x,再+1
算术运算符的优先级
算术运算符的优先级由高到低依次为自增自减(++、−)和取负(−)、乘法除法(*、/)和取模(%)、加和减(+、-)。
3.1.3. 赋值运算符
简单赋值运算符
1.从右到左 2.复制粘贴
变量=表达式
复合赋值运算符

变量 基本运算符=表达式
3.1.4. 关系运算符

3.1.5. 逻辑运算符

3.1.6. 条件运算符
? :
三目运算符
3.1.7. 位运算符
按位运算符
按位或运算符
按位非运算符
按位异或运算符
移位运算符
3.1.8. 其他运算符
,:逗号运算符;把几个表达式串在一起 *:指针运算符(单目运算符); 返回于某个地址内存储的变量值 &:取地址运算符(单目运算符); 返回操作数的地址 []:下标运算符; 用来表示数组中的某个元素 .:结构体成员运算符; ->:指向结构体成员运算符; sizeof:长度运算符(单目运算符)。
3.1.9. 运算符优先级和结合性

优先级
3.2. 表达式
3.2.1. 算术表达式
3.2.2. 赋值表达式
3.2.3. 逗号表达式
3.2.4. 关系和逻辑表达式
关系表达式
#include <stdio.h> //头文件 void main() //主函数 { int a,b,c,d; //声明变量 a=4; //赋值 b=5; c=(a>b); //计算关系表达式 d=(a!=b); printf("c=%d\nd=%d\n",c,d); //输出结果 }
逻辑表达式
#include <stdio.h> //头文件 void main() //主函数 { int a,b,c,d; //声明变量 a=4; //变量赋值 b=5; c=a||b; //计算逻辑表达式 d=!a; printf("c=%d\nd=%d\n",c,d); //输出结果 }
4. C语言及流程控制结构
4.1. 说明语句
int a=1; //声明并初始化整型变量 long count=10000; //声明并初始化长整型变量 float c; //声明浮点型变量 char p[6]="first"; //声明并初始化字符数组
4.1.1. 类型说明符 变量名(=初始值)
4.2. 表达式语句
4.2.1. 表达式;
4.3. 复合语句
C语言中由单个表达式和末尾的分号构成的语句是简单语句。而C语言作为结构化程序设计语言的重要特色就是支持复合语句。复合语句是用花括号“{}”将一组语句组合在一起而构成的语句
4.4. 循环语句
4.4.1. while循环
#include <stdio.h> //头文件 void main() //主函数 { int i=100; //初始化 int sum=0; //初始化 while(i>0) //while循环 { sum=sum+i; //累加 i--; //控制循环次数 } printf("sum=%d\n",sum); //输出结果 }
4.4.2. do...while循环语句
#include <stdio.h> //头文件 void main() //主函数 { int i=100; //初始化 int sum=0; //初始化 do //do⋯while循环 { sum=sum+i; //表达式语句 i--; }while(i>0); //表达式 printf("sum=%d\n",sum); //输出结果 }
4.4.3. for循环语句
#include <stdio.h> //头文件 void main() //主函数 { int i; //初始化 int sum=0; //初始化 for(i=0;i<=100;i++) //for循环 { sum=sum+i; //表达式语句 } printf("sum=%d\n",sum); //输出结果 } 注意for循环 for(a;b;c) a b c都可以省略 当a 省略 可以把a放外面 当c 省略,可以把c放循环体内部 当abc都省略,就是死循环
4.5. 条件语句
4.5.1. 单分支if条件结构
include <stdio.h> //头文件 void main() //主函数 { int a,b; //变量声明 a=1; //初始化 b=1; //初始化 if(a==b) { a++; //if语句的单分支结构 } printf("a=%d\n",a); //输出结果 }
4.5.2. 双分支if条件结构
#include <stdio.h> //头文件 void main() //主函数 { int a,b; //变量声明 a=1; //初始化 b=2; //初始化 if (a==b) //if语句的双分支结构 { a++; } else { a--; } printf("a=%d\n",a); //输出结果 } //判断闰年 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int year; scanf("%d", &year); if (year % 400 == 0 || year % 4 == 0 && year % 100 != 0) { printf("yes\n"); } else { printf("no\n"); } }
4.5.3. 3阶梯if...else...if条件结构
#include <stdio.h> //头文件 void main() //主函数 { int score; char grade; score=87; //初始化 if(score>=95) //进入多分支判断语句 { grade='A'; } else if(score>=80) { grade='B'; } else if(score>=70) { grade='C'; } else if(score>=60) { grade='D'; } else { grade='E'; } printf("score=%d\ngrade=%c\n",score,grade); //输出结果 }
4.6. 开关语句
include <stdio.h> //头文件 void main() //主程序 { char ch; ch='a'; //赋值 switch(ch) //开关语句 { case 'a': //如果为a,则输出A printf("ch的大写字符为A\n"); break; case 'b': //如果为b,则输出B printf("ch的大写字符为B\n"); break; default: //否则输出这里 printf("Not a and b\n"); break; } }
4.7. 跳转语句
4.7.1. goto
4.7.2. break
在C语言程序设计中,break语句主要用于如下两种情况。 当break用于开关语句switch中时,可使程序跳出switch,而执行switch以后的语句。如果没有break语句,则switch语句将成为一个死循环而无法退出。 在do…while、for、while循环语句中时,break语句和if语句联在一起使用,可以实现满足条件时便跳出循环的操作。 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //从1加到100,当和大于2000,就终止循环 int main() { int i, total; //for语句中只能有两个分号 for (i = 1, total = 0; i <= 100; i++)//for循环后不能加分号 { if (total > 2000) { break;//当求和大于2000,就终止循环 } total = total + i; } printf("total=%d,i=%d\n", total,i); } break的作用是结束整个循环
4.7.3. continue
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //从1到100之间奇数求和 int main() { int i, total; //for语句中只能有两个分号 for (i = 1, total = 0; i <= 100; i++)//for循环后不能加分号 { if (i % 2==0)//如何i是偶数 { continue;//提前结束本轮循环 } total = total + i; } printf("total=%d\n", total); } //continue 是结束最近的一次循环,
4.8. 函数调用语句
#include <stdio.h> //头文件 void myprint() //定义函数 { printf("hello world.\n"); //输出字符串 } int Add(int a) //定义函数 { return a+1; //返回值 } void main() //主函数 { int i=2,j; //初始化 myprint (); //调用函数语句 j=Add(i); //调用带有返回值的函数语句 printf("%d+1=%d\n",i,j); //输出结果 }
4.9. 空语句
#include <stdio.h> //头文件 void main() //主函数 { int a; printf("First output\n"); //输出字符串 for (a=0;a<50000;a++) { ; } printf("Delay some times and output\n"); //输出字符串 } 这段程序中,for循环语句内只有一个分号“;”,构成一个空语句。这样的程序执行没有任何实际意义,唯一可以实现的是消耗CPU时间,常用来程序延时
4.10. 返回语句
4.10.1. return
5. 格式化输入/输出
5.1. 格式化输出函数printf()
5.1.1. printf函数格式
printf(格式控制,输出列表)
5.1.2. printf()函数的格式字符

5.1.3. printf()函数的修饰符
标志

输出最小宽度
#include <stdio.h> #include <stdlib.h> int main() { int i,j; //声明变量 i=-78; //初始化 j=65535; printf("%d%d\n",i,j); //输出各种格式 printf("%4d%4d\n",i,j); printf("%-4d%-4d\n",i,j); printf("%+4d%+4d\n",i,j); printf("%#x%#X\n",j,j); printf("%04d%04d\n",i,j); printf("% d% d\n",i,j); system("pause"); return 0; } 第1个printf()函数只使用格式字符输出整型变量,因两个格式字符是紧靠着的,所以输入的两个数也紧靠在一起,看起来像一个数一样。 第2个printf()函数设置输出每个变量至少占4个字符宽,变量i的值为2位数,加上负号一共占3位,所以左侧空一个字符位(默认是右对齐的)。变量j的值有5位数,超过了4位数的宽度要求,将紧接着前一个输出项输出全部的5位数。 第3个printf()函数在格式字符中使用“-”修饰符,并设置输出每个变量至少占4位宽度,这时变量i的值占3位,但其输出内容靠左对齐,右侧留一个空格。变量j的值占5位,超过了最小宽度,紧接着上一个输出项输出。 第4个printf()函数使用修饰符“+”,使用输出的正数也显示正号。 第5个printf()函数使用修饰符“#”,使输出的十六进制数加上前缀“0x”或“0X”。 第6个printf()函数使用修饰符“0”,并设置最小宽度为4,变量i的值占3位,将在该数前面填充一个0。变量j的值占5位,将不会填充前置0。 第7个printf()函数使用修饰符空格,变量i为负数,仍然输出负号,变量i为正数,将不显示正号,而显示一个空格。
精度
include <stdio.h> include <stdlib.h> int main() { int j; //声明变量 double f; j=65535; //初始化 f=3.1415926; printf("j=%.8d\n",j); //输出语句 printf("f=%.2f\n",f); printf("f=%07.2f\n",f); system("pause"); return 0; }
长度
5.1.4. 典型的printf()函数实例
输出字符
输出整数
输出实数
输出字符串
5.1.5. 动态设置输出宽度和精度
5.1.6. printf()函数的返回值
5.2. 格式化输入函数scanf()
5.2.1. scanf()函数的格式
scanf("格式字符串",地址表列);
#include <stdio.h> #include <stdlib.h> int main() { int a,b; //声明变量 printf("请输入两个整数:"); scanf("%d%d",&a,&b); //输入 printf("您输入的两个整数a=%d,b=%d\n",a,b); //输出 system("pause"); return 0; }
5.2.2. scanf()函数格式字符串
类型
d或i:输入十进制整数; o:输入八进制整数; x或(X):输入十六进制整数; u:输入无符号十进制整数; f、e、g:输入实型数(用小数形式或指数形式); c:输入单个字符; s:输入字符串。
长度
宽度
星号*
5.2.3. 循环读取字符
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { char c; while (scanf("%c", &c) != EOF) //EOF= -1 { if (c != '\n')//判断,后面有一个小括号,小括号里边是一个表达式 { printf("%c", c - 32); } else {//else就是否则 printf("\n"); } } }
5.3. 其他常用的输入/输出函数
5.3.1. 字符读入函数getchar()
char c c=getchar(); #include <stdio.h> #include <stdlib.h> int main() { char c; //声明变量 printf("输入一个字符:"); c=getchar(); //输入字符 printf("输入的字符是:%c\n",c); system("pause"); return 0; }
只能读入一个字符
5.3.2. 字符读入函数getch()
1.getch现在新版本不让用了,改用_getch() 2.getch()和getchar()的功能基本相同唯一不同的是,getch()输入的值,不显示 只要用户按下一个键,函数立刻返回 3.常用语程序调试中
案例
#include <stdio.h> #include <stdlib.h> #include <conio.h> int main() { char c; printf("按任意键继续:"); c = _getch(); printf("按得键是:%c\n", c); } //控制台显示 // 按任意键继续: // 按得键是 : D
5.3.3. 字符串读入函数gets()
#include <stdio.h> #include <stdlib.h> #include <conio.h> int main() { char string[80]; printf("请输入一个字符:\n"); gets(string); printf("输入的字符串是:%s\n", string); }
gets()和scanf()的区别
scanf()在读取字符串中如果有空格,scanf()只读取空格前的数据 而gets()读取完整字符串包括空格,到换行符停止 一般接受用户输入字符串时,使用gets()更好
5.3.4. 字符输出函数putch()
#include <stdio.h> #include <stdlib.h> #include <conio.h> int main() { char c1='G',c2='o',c3='o',c4='d'; //声明并初始化 putch (c1); //输出 putch (c2); putch (c3); putch (c4); putchar('\n'); //换行 system("pause"); return 0; }
5.3.5. 字符输出函数putchar()
等同于putch()
5.3.6. 字符串输出函数puts()
#include <stdio.h> #include <stdlib.h> int main() { char str[80]; puts("请输入一个字符串:"); gets(str); puts("输入的字符串是:"); puts(str); }
5.3.7. 格式化内存缓冲区输出函数sprintf()
6. 数组
6.1. 了解数组
6.1.1. 数组的好处
6.1.2. 数组的概念
6.1.3. 数组的维数
6.2. 一维数组
6.2.1. 一维数组的声明
一维数组的声明
int arr[30];
6.2.2. 一维数组的存储
int a[5]; 5*4=20个字节
6.2.3. 一维数组的引用
6.2.4. 向函数传递一维数组
6.2.5. 一维数组的初始化
int a[10]={0}; //使数组中所有元素的初始化为0;
6.2.6. 案例
一维数组+函数+形参实参
#include <stdio.h> //打印数组里的每一个元素,数组在传递时,元素个数传递不过去 //print是我们自定义的一个函数 void print(int b[], int len) { int i; for (i = 0; i < len; i++) { printf("a[%d]=%d\n", i, b[i]); } b[4] = 20;//在子函数中修改数组元素 } int main() { //定义数组就是写一个变量名,后面加上方括号,方括号内写上整型常量 //定义数组的一瞬间,数组占据的空间大小就确定下来了 int j = 10; int a[5] = { 1,2,3,4,5 }; int i = 3; //a[5] = 20;//访问越界,访问了不属于你自己的空间 //a[6] = 21; //a[7] = 22; //printf("j=%d\n", j); print(a, 5);//调用函数 printf("a[4]=%d\n", a[4]); return 0; }
字符数组
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1 int main() { char c[6] = { 'h','e','l','l','o' }; char d[5] = "how"; printf("%s----%s\n", c, d);//printf的%s,对应后面要写的是字符数组名,字符串常量 char e[20],f[20]; scanf("%s%s", e,f); printf("%s---%s\n", e,f); }
修改字符数组,首字母大写
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //我们把d称为形参 void print(char d[]) { int i = 0; while (d[i]) { printf("%c", d[i]);//字符数组去输出某一个元素时,用%c i++; } printf("\n"); //修改字符数组中的字符串的内容,把首字母变大写 d[0] = d[0] - 32; } int main() { char c[10] = "hello";//c里边是10,或者20都可以,只要大于等于6即可 print(c);//我们把c称为实参,调用print函数时,是d=c printf("%s\n", c); return 0; }
str函数的使用
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> //char* strcpy(char* to, const char* from); 有const修饰代表这个地方可以放一个字符串常量 int main() { char c[20] = "wangdao"; printf("数组c内字符串的长度=%d\n", strlen(c)); char d[20]; strcpy(d, "study"); puts(d); //下面是看strcmp,两个字符串比较,是比较对应字符位置的ascii码值 int ret = strcmp("hello", "how"); printf("两个字符串比较后的结果=%d\n", ret); //下面看strcat,拼接两个字符串,目标数组要能够容纳拼接后的字符串 strcat(c, d); puts(c); return 0; }
6.3. 二维数组
6.3.1. 二维数组的声明
6.3.2. 二维数组的存储
6.3.3. 二维数组的初始化
6.3.4. 字符数组和
6.4. 字符数组和字符串
6.4.1. 一维字符串数组
在程序声明字符数组时,必须声明一个比要存的字符串多一个字符的数组,最后一位用来存储空字符"\0" 1字符串数组的声明 char name[10],char address[30]; 2.字符串数组的初始化 char name[10]={'a','b','c','d','e'}; 3.应用
6.4.2. 了解字符串
字符和字符串的区别 字符是以单引号括起来字符,一个字符只占用一个字节的存储单元。 字符串是以双引号括起来的一个或多个字符,C编译器将自动在其后添加一个结束标志“\0”。因此字符串占用的内存大小为:字符串实际字符数+1。 char name[] = "dingkang"; //占用 9个字节
6.4.3. 字符串的操作函数
strlen(s1)函数返回s1的长度。 strcpy(s1,s2)函数将s2复制到s1。 strcat(s1,s2)函数将s2连接到s1的末尾。 strcmp(s1,s2)函数若s1与s2相等,返回值为0;若s1<s2,返回值小于0;若s1>s2,返回值大于0。 字符串操作头文件 (字符串一个字符一个字符比较,按ASCII码比较) #include <string.h>
演示案例
#pragma warning (disable:4996) #include <stdio.h> //头文件 #include <stdlib.h> #include <string.h> //提供字符串操作的头文件 int main() { char a[10] = { 'a','b','\0'}; //声明字符串数组,并赋值 char b[] = "good"; char s[30]; printf("字符串a的长度为%d\n", strlen(a)); //输出字符串a的长度 printf("字符串b的长度为%d\n", strlen(b)); //输出字符串b的长度 strcat(a,b); //将字符串b追加到a的末尾 printf("a=%s,b=%s\n", a, b); //输出 strcpy(s, a); //将字符串a复制到字符串s printf("s=%s\n", s); //输出字符串s return 0; }
strcmp演示
#include<stdio.h> #include<string.h> int main() { char* p1 = "abcdef"; char* p2 = "abcdef"; char* p3 = "abcd"; char* p4 = "bcde"; printf("%d\n", strcmp(p1,p2 )); printf("%d\n", strcmp(p1,p3 )); printf("%d\n", strcmp(p3,p4 )); } strcmp比较两个字符串的大小,一个字符一个字符比较,按ASCLL码比较 标准规定: 第一个字符串大于第二个字符串,则返回大于0的数字 第一个字符串等于第二个字符串,则返回0 (完全一样) 第一个字符串小于第二个字符串,则返回小于0的数字
6.4.4. 字符串的输入输出
#pragma warning (disable:4996) #include <stdio.h> //头文件 #include <stdlib.h> int main() { char name[30]; printf("请输入您的姓名(10个字符以内):"); scanf("%s", name); printf("输出您的名字;%s\n",name); } //本例中,没有使用取地址符&,因为数组本身就是一个地址,当然使用&name这种方式也可获得数组的首地址
6.4.5. 二维字符串数组
6.5. 多维数组
6.6. 数组的基本应用
6.6.1. 反转字符串
6.6.2. 查找最大值
#include <stdio.h> //头文件 #include <stdlib.h> int main() { float arr[10],max; //声明变量 int i; printf("请输入10个数:\n"); for(i=0;i<10;i++) //输入数据 { scanf("%f",&arr[i]); } max=arr[0]; for(i=1;i<10;i++) //查找最大值 { if(arr[i]>max) max=arr[i]; } printf("您输入的10个数为:"); for(i=0;i<10;i++) //输出 { printf("% .2f ",arr[i]); } printf("\n最大数为:%.2f\n",max); system("pause"); return 0; }
6.6.3. 冒泡排序
#include <stdio.h> //头文件 #include <stdlib.h> #define N 10 //宏定义 int main() { int a[N],i,j,temp; //声明变量 printf("输入%d个整数:",N); for(i=0;i<N;i++) //输入数据 { scanf("%d",&a[i]); } for(i=0;i<N-1;i++) //排序 { for(j=i;j<N; j++) if(a[i]<a[j]) { temp=a[i]; a[i]=a[j]; a[j]=temp; } } printf("排序结果:"); for(i=0;i<10;i++) //输出数据 { printf("%d ",a[i]); } printf("\n"); system("pause"); return 0; }
7. 指针
7.1. 指针的本质
#include <stdio.h> //&符号是取地址,指针变量的初始化一定是某个变量取地址 int main() { int i = 5; int* p=&i; printf("i=%d\n", i);//直接访问 printf("*p=%d\n", *p);//间接访问 return 0; }
7.2. 指针的使用场景
7.2.1. 指针的传递
#include <stdio.h> void change(int *j)//j称为形参,j=&i { *j = 5;//指针的间接访问 } int main() { int i = 10;//i是局部变量 printf("before change i=%d\n", i); change(&i);//函数调用时,把&i称为实参 printf("after change i=%d\n", i); return 0; } //一般来讲,形参无法改变实参,但是我们可以用指针的传递,改实参地址上的数据
7.2.2. 指针的偏移
#include <stdio.h> int main() { int a[5] = { 1,2,3,4,5 }; int* p;//对一个指针变量进行取值,得到的类型是其基类型 p = a;//这种写法实际上是p保存a的首字母 printf("*p=%d\n", *p); for (int i = 0; i < 5; i++) { printf("%d\n", *(p + i)); } return 0; } //很经典,用指针,遍历数组
7.2.3. 指针的自增、自减运算符
#include <stdio.h> //重要程度低一些,不理解直接放弃,不重要 int main() { int a[3] = { 2,7,8 }; int* p; int j; p = a;//让指针变量p,指向数组的开头 j = *p++;//j=*p;(*p)++,第一步:任何时候都是把后加加去掉,第二步另外一个运算符看优先级是否高于++ printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);//2 2 7 j = p[0]++;//j=p[0];p[0]++; printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);//2,7,8 return 0; }
7.2.4. 指针与一维数组
#include <stdio.h> //数组名作为实参传递给子函数时,是弱化为指针的 //练习传递与偏移 void change(char *d) { *d = 'H'; d[1] = 'E'; *(d + 2) = 'L'; } int main() { char c[10] = "hello"; change(c); puts(c); return 0; }
7.2.5. 指针的动态内存申请
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //malloc可以帮我们实现动态数组 int main() { int i;//申请多大的空间 scanf("%d", &i); char* p; p = (char*)malloc(i);//malloc申请空间的单位是字节 strcpy(p, "malloc success"); puts(p); free(p);//释放空间,p的值必须和最初malloc返回的值一致 p = NULL;//如果不把p值为NULL,把p称为野指针,NULL就是0 return 0; }
7.2.6. 堆空间和栈空间的差异
#include <stdio.h> char* print_stack() { char c[17] = "I am print_stack"; puts(c);//能正常打印 return c; } char* print_malloc() { char* p = (char*)malloc(30); strcpy(p, "I am print_malloc"); puts(p); return p; } int main() { char* p; p = print_stack();//栈空间会随着函数的执行结束而释放,相当于p=c //puts(p);//打印不出来 p = print_malloc();//堆空间不会随子函数的结束而释放,必须自己free puts(p); free(p); return 0; }
7.2.7. 字符指针和字符数组的初始化
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char* p = "hello"; //把字符串型常量"hello"的首地址赋给p char c[10] = "hello"; //等价于strcpy(c,"hello"); c[0] = 'H'; //p[0]='H'; //不可以对常量区数据进行修改 printf("c[0]=%c\n", c[0]); printf("p[0]=%c\n", p[0]); p = "world"; //将字符串world的地址赋给p //c="world"; //非法 puts(p); return 0; }
7.3. 二级指针
7.3.1. 二级指针的偏移
#include <stdio.h> #include <stdlib.h> void change(int** p, int* pj) { *p = pj; } //这部分对于考研不重要,记忆一下即可 ,初试和机试都不用 //要想在子函数中改变一个变量的值,必须把该变量的地址传进去 //要想在子函数中改变一个指针变量的值,必须把该指针变量的地址传进去 int main() { int i = 10; int j = 5; int* pi; int* pj; pi = &i; pj = &j; printf("i=%d,*pi=%d,*pj=%d\n", i, *pi, *pj);//i和*pi都是10,*pj是5 change(&pi, pj); printf("after change i=%d,*pi=%d,*pj=%d\n", i, *pi, *pj);//目标是让*pi的值为5 return 0; }
7.4. 指针数组
char *p[5];
7.5. 指向指针的指针
7.6. 指针函数
7.6.1. 返回指针的函数
在C语言中,还允许一个函数的返回值是一个指针(即地址),这种返回指针的函数称为指针型函数 定义指针型函数的形式如下: 类型说明符 *函数名(形参表) { ⋯ /*函数体*/ } 其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。 类型说明符表示了返回的指针值所指向的数据类型。一般用这种函数返回一个字符串常量的首地址。
8. 函数
函数的两个功能 封装代码 进行结构化程序设计 使用C语言函数时候,应注意以下几点: C语言的源程序的函数数目是不限的。 在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。 函数之间允许相互调用,也允许“嵌套调用”。 函数还可以自己调用自己,称为“递归调用”。 main()函数是主函数,它可以调用其他函数,而不允许被其他函数调用。
8.1. 函数的嵌套调用
8.2. 全局变量
8.3. 递归调用
8.4. 函数的概念
8.4.1. 函数的分类
1.从函数定义角度分类 库函数 字符类型分类函数 转换函数 输入输出函数 字符串函数 数学函数 日期和时间函数 其他函数 自定义函数 2.从有无返回值角度考虑 又返回值的函数 无返回值的函数 3.从数据传送角度考虑 无参函数 有参函数
8.4.2. 函数的定义
返回类型 函数名(参数表) { 函数实现过程(函数体) return表达式; } 返回类型:可以是任何类型,除了数组,一般用void 不返回 函数名:标识函数的名称 参数表 函数体 return:用于返回函数执行的结果,如果没有可以不写
举例
#include <stdio.h> #include <stdlib.h> int int_max(int m, int n);//定义函数,用于求两个数的最大值 int main() { int i, j, m; printf("输入两个整数\n"); scanf_s("%d%d", &i, &j); //输入整数 m = int_max(i, j); //函数调用 printf("最大数为:%d\n", m); return 0; } int int_max(int m, int n) //自定义函数 { if (m > n) { return m; } else { return n; } }
关于函数的声明
网上说: 如果自定义函数在main()函数前面,则可以不声明函数 如果自定义函数在main()函数的后面,则必须要声明函数才行哦 但是经过测试,好像不管在main()的前后,不声明函数,都可以正常运行
8.4.3. main()函数
main()函数也可以带参数 形式:int main(int argc,char *argv[ ]) 参数包括一个整型和一个指针数组 整型参数:表示被调函数所带命令行的参数的数目 指针数组参数:每个元素是指向命令行参数的指针,即每个指针对应一个字符串,而第一个指针通常指向命令名字符串。 main()函数的返回值:主要用于向系统返回一个运行状态码。 带参数的main()函数主要用于复杂应用程序的过程间通信,对于一般的单一程序是不起任何作用的。因此void main() int main() 就可以了
8.5. 函数的工作过程
8.5.1. 程序结构
8.5.2. 函数执行过程
8.5.3. 编写函数
8.6. 函数的参数
8.6.1. 形参和实参
实参和形参在数量、类型、顺序上应保持严格一致,否则会因类型不匹配而导致错误。 形参只有在函数内部有效。因为形参变量只有在被调用时才分配内存单元,在调用结束后,将立即释放内存单元。因此,函数调用结束并返回主调函数后,则不能再使用该形参变量。 实参可以是常量、变量、表达式等,无论实参是何种类型的量,在进行函数调用时,它们都必须先赋予确定的值,以便把这些值传送给形参。 实参出现在主调函数中,进入被调函数后,实参变量将不能再使用。 在函数调用时,数据传送是单向的从实参传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
8.6.2. 参数传递过程
值调用(赋值调用)
值调用:将实参复制一份,到形参当中
引用调用
引用调用:就是将实参的地址传递给函数的形参,使形参和实参共用同一个地址 方法:在形参的参数前面加*,表示为一个指针,即对应该实参应该是一个保存想同类型的数据地址
案例SWAP()
#include<stdio.h> #include<stdlib.h> //函数 引用调用 int swap(int* a, int* b); int main() { int i=5,j=10; swap(&i, &j); printf("i=%d,j=%d", i, j); } int swap(int* a, int* b) { int temp; temp = *a; *a= *b; *b = temp; }
8.6.3. 数组作为函数参数
8.6.4. 指针作为函数参数
8.6.5. main()函数的参数
8.7. 函数调用
8.7.1. 函数调用方式
单独语句调用
例如 printf("i=%d",i);
将函数嵌入表达式
例如 m=int_max(a,b);
将函数作为参数
例如:printf("最大值为%d\n",int_max(a,b));
8.7.2. 被调函数说明
8.7.3. 函数的返回值
void 不需要返回值 return 1.返回值2.终止程序
8.8. 几种典型的函数调用形式
8.8.1. 赋值调用
8.8.2. 引用调用
参考参数传递过程
8.8.3. 嵌套调用
嵌套调用:在被调用函数中又调用了其他函数的调用形式 (各种函数除了主函数外,互相使用)
案例,求最大值
#include <stdio.h> #include <stdlib.h> int main() { int a, b, c,m; printf("请输入三个数\n"); scanf_s("%d%d%d", &a ,& b, & c); printf("三个数中的最大值为%d", max3(a, b, c)); } int max2(int a, int b) { return (a > b) ? a : b; /*三目运算符*/ } int max3(int a, int b, int c) { int m = max2(a, b); int n = max2(b, c); return max2(m, n); }
8.8.4. 递归调用
#include <stdio.h> //函数自己调用自己就是递归,初试考的概率极低的,机试有用到 //计算5*4*3*2*1 int f(int n) { if (1 == n) { return 1;//一定写结束条件 } return n * f(n - 1);//第一步是写好公式 } int main() { int n = 5; int result = f(n); printf("result=%d\n", result); }
递归基本原理
9. 存储管理
9.1. 内存组织方式
9.1.1. 内存组织方式
C语言内存示意图

三种内存分配方式
静态存储分配
是指在编译时就能确定每个变量在运行时需要占用的内存空间,因而在编译时就可以给这些变量分配固定的内存空间。 对于全局变量、静态变量使用这种方式分配。
栈式存储分配
栈式存储分配,也可称为动态存储分配,由一个类似于堆栈的运行栈来实现。和静态存储分配不同,在栈式存储方案中,程序变量对内存空间的需求在编译时不进行分配,到运行时才进行具体的分配。在使用栈式存储分配时,编译器在编译时也需要知道函数所需数据区域的大小,然后在程序运行时使用该值分配内存。 C语言函数内部的局部变量都是采用栈式分配。
堆式存储分配
堆式存储分配,堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。当程序编译无法确定数据区域大小时(如可变长度的字符串),可在堆中分配内存空间。 C语言程序在运行时进行的动态内存分配,都是在堆中进行的。 malloc
9.1.2. 堆和栈的比较
内存分配和回收方式不同:栈由编译器自动分配和释放,用来存放函数的参数值、局部变量的值等。而堆一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。在C语言中,可通过malloc、free等函数操作堆。 存放内容不同:栈中主要存放函数调用中的参数、函数的局部变量(静态变量不入栈),通过栈才能完成函数之间调用时的现场保存。而堆的存放内容没有限制,由程序员安排。 大小限制不同:在DOS、Windows操作系统中,栈是由高地址向低地址扩展的数据结构,是一块连续的内存的区域。也就是说,栈顶的地址和栈的最大容量是系统预先规定好的,如果申请的空间超过栈的剩余空间时,将提示“堆栈溢出”。所以,栈的空间是有限的,例如,执行一个无结束条件的递归调用,可看到堆栈溢出的提示。而堆是由低地址向高地址扩展的数据结构,可以是不连续的内存区域。系统用链表将这些不连续的空闲内存地址连接起来。堆的大小受限于计算机系统中有效的虚拟内存。所以,堆的空间很大,也比较灵活。 效率不同:栈是由系统自动分配,速度较快。而堆是由malloc等函数分配的内存,一般速度比较慢,而且容易产生内存碎片。
9.1.3. C语言的动态存储管理
9.2. C语言的动态存储管理
分配内存函数——malloc( ); 分配内存的函数——calloc( ); 调整已分配内存——realloc( ); 释放分配的内存——free( );
9.3. 分配内存函数-malloc()
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //malloc可以帮我们实现动态数组 int main() { int i;//申请多大的空间 scanf("%d", &i);//读取一个整型数 char* p; p = (char*)malloc(i);//malloc申请空间的单位是字节,不进行强制类型转换会有警告 char c; scanf("%c", &c);//为了去除缓冲区里边的\n,VS可以用rewind,OJ不行,所以不能用rewind gets(p); puts(p); return 0; }
9.3.1. 案例:制作工资表
#include <stdio.h> #include <stdlib.h> int main() { double* pd, sum = 0; int i, n; printf("输入员工数量:"); scanf_s("%d", &n); //输入员工数量 if (n <= 0) { printf("员工数量必须大于0!\n"); getch( ); exit(1); } pd = (double*)malloc(n * sizeof(double)); if (!pd) //检测内存是否正确分配 { printf("创建动态数组失败"); getch(); exit(1); } for (i = 0; i < n; i++) { printf("第%d个员工的工资:", i + 1); scanf_s("%lf", &pd[i]); } printf("\n员工工资列表\n"); printf("编号\t工资\n"); for(i = 0; i < n; i++) //循环输出工资信息 { printf("% d\t % .2f\n", i + 1, pd[i]); sum += pd[i]; } printf("\n工资总额:%.2f\t平均工资:%.2f\n", sum, sum / (double)n); return 0; }
9.4. 代初始化的分配内存函数calloc()
pd=(double *)malloc(n*sizeof(double)); //动态分配 pd=(double *)calloc(n,sizeof(double)); //带初始化的动态内存分配 calloc相比malloc只是多个空间初始化,一般情况下二者可以互用
9.5. 调整已分配内存函数——realloc()
9.6. 释放分配的内存函数——free()
10. 结构
10.1. 结构的定义
struct 结构名 { 类型说明符 成员名1; 类型说明符 成员名2; 。。。 类型说明符 成员名n; } 例子 struct date { int year; int month; int day; }; 因为编译器不会为数据类型分配存储空间,所以,定义结构类型并不会引起内存分配
10.2. 结构变量的定义
10.2.1. 先定义结构,再定义结构变量
struct student //定义结构类型 { char name[20]; //类型说明 int num; int age; char sex[2]; float score; }; struct student Alice,Bob //结构变量
10.2.2. 定义结构的同时,定义结构变量
struct student //定义结构类型 { char name[20]; //类型说明 int num; int age; char sex[2]; float score; } Alice,Bob ; //结构变量
10.2.3. typedef struct的应用
#include <stdio.h> struct people { int age; int id; }a;//a代表什么? people是类 这里的a是对象 typedef struct cat { int age; int id; }b; 这里的cat是类,同时将cat定义了一个新名字b,也是个类 int main() { a.age = 20; printf("%d\n", a.age); //cat结构体被typedef修饰,表示已经把cat结构体定义为一种类型,这样就可以这样定义一个cat类型的变量b b1,b tom,b robin b tom; tom.age=100; printf("%d\n", tom.age); return 0; }
10.3. 结构变量的使用
结构变量.结构成员
10.3.1. 案例,出生日期
#include <stdio.h> //头文件 #include <stdlib.h> int main() //主函数 { struct date //声明结构 { short year; char month; char day; }birthday; //结构变量 printf("请输入出生日期(按yyyy/mm/dd格式输入):\n"); scanf_s("%4d/%2d/%2d", &birthday.year, &birthday.month, &birthday.day); if (birthday.year < 1900 || //判断 birthday.year>2008 || birthday.month < 1 || birthday.month>12 || birthday.day < 1 || birthday.day>31) printf("日期输入错误!\n"); else printf("您的生日是:%4d/%02d/%02d\n", birthday.year, birthday.month, birthday.day); //输出 return 0; }
10.4. 结构变量的初始化
struct date { short int year; char month; char day; }birthday={1976,10,1};
10.5. 结构数组
10.5.1. 结构数组的定义和引用
定义
1.定义 首先定义结构 例如 struct minneed { char* name; char sex; unsigned short age; float amount; }; 然后定义结构数组 struct minneed needs[2]; 或者 struct minneed { char* name; char sex; unsigned short age; float amount; }needs[2];
引用
needs[0].name=needs[1].name; needs[0].sex=needs[1].sex; needs[0].age=needs[1].age; needs[0].amount=needs[1].amount;
初始化
struct minneed { char *name; char sex; unsigned short age; float amount; }; struct minneed needs[4]= {{"zhangjun",1,55,150.0}, {"wumei",0,48,130.0}, {"duli",0,55,180.0}, {"liping",1,56,150.0} };
10.6. 结构指针
10.6.1. 定义结构指针
struct student //定义结构 { char *name; //姓名 int num; //学号 int age; //年龄 char *sex; //性别 float score; //分数 }*pstu; //声明结构指针 结构指针和其他指针一样,也要先赋值才能使用 struct minned need1; struct stu *pneed; pneed = &need1; 
10.6.2. 结构指针的引用
(*结构指针变量).成员名 例如 (*pneed).name; //两侧的()不能省略,因为句点运算“.”的优先级高于指针运算符 简写: pneed->name; 三种访问name need1.name (*pneed).name pneed->name #include <stdio.h> //头文件 #include <stdlib.h> struct student //定义结构 { char* name; //姓名 int num; //学号 int age; //年龄 char* sex; //性别 float score; //分数 }Bob = { "Bob",101,24," M ",90.0 }; //声明并初始化结构变量 int main() { struct student* pstu; //定义结构指针 pstu = &Bob; //为结构指针赋值 //采用方式1输出成员值 printf("Name=%s,Num=%d\n", Bob.name, Bob.num); printf("Age=%d,Sex=%s,score=%f\n", Bob.age, Bob.sex, Bob.score); //采用方式2输出成员值 printf("Name=%s,Num=%d\n", (*pstu).name, (*pstu).num); printf("Age=%d,Sex=%s,score=%f\n", (*pstu).age, (*pstu).sex, (*pstu).score); //采用方式3输出成员值 printf("Name=%s,Num=%d\n", pstu->name, pstu->num); printf("Age=%d,Sex=%s,score=%f\n", pstu->age, pstu->sex, pstu->score); return 0; }
10.6.3. 用指针处理结构数组
#include <stdio.h>//头文件 #include <stdlib.h> #define N 4 //宏定义 struct minneed //定义结构 { char* name; char sex; unsigned short age; float amount; }; int main() { struct minneed needs[N] = //结构数组 { {"zhangjun",1,55,150.0}, {"wumei",0,48,130.0}, {"duli",0,55,180.0}, {"liping",1,56,150.0} }; struct minneed* pneed; //结构指针 int i; pneed = needs; for (i = 1; i < N; i++) //输出 { printf("\n姓名:%s\n",pneed->name); printf("性别:%s\n",(pneed->sex == 1) ? "男" : "女"); printf("年龄:%d\n",pneed->age); printf("地址:%.2f\n",pneed->amount); pneed++; } return 0; } pneed++ 结构指针自增运算 
10.7. 嵌套结构
10.7.1. 包含数组的结构
10.7.2. 包含指针的结构
10.7.3. 包含结构的结构
#include <stdio.h> //头文件 #include <stdlib.h> struct date //定义结构 { short int year; char month; char day; }; struct emp //定义嵌套结构 { char name[10]; char address[20]; char sex; struct date birthday; struct date entryday; }; int main() //主函数 { struct emp emp1; printf("请输入员工姓名:"); //输入信息 scanf_s("%s", emp1.name); printf("请输入员工住址:"); scanf_s("%s", emp1.address); printf("请输入员工性别(1-男,0-女):"); scanf_s("%d", &emp1.sex); printf("请输入出生日期(按yyyy/mm/dd格式):"); scanf_s("%4d/%2d/%2d", &emp1.birthday.year, &emp1.birthday.month, &emp1.birthday.day); printf("请输入聘用日期(按yyyy/mm/dd格式):"); scanf_s("%4d/%2d/%2d", &emp1.entryday.year, &emp1.entryday.month, &emp1.entryday.day); printf("\n员工基本信息:\n"); //输出信息 printf("姓名:%s\n", emp1.name); printf("住址:%s\n", emp1.address); printf("性别:%s\n", (emp1.sex == 1) ? "男" : "女"); printf("出生日期:%4d/%02d/%02d\n", emp1.birthday.year, emp1.birthday.month, emp1.birthday.day); printf("聘用日期:%4d/%02d/%02d\n", emp1.entryday.year, emp1.entryday.month, emp1.entryday.day); return 0; }
10.8. 向函数传递结构
10.8.1. 向函数传递变量的值
10.8.2. 传递结构指针到函数
11. 联合、枚举、位域
11.1. 联合
是指将不同的变量组织成一个整体的数据类型。其中的这些变量在内存中占用同一段存储单元,而在不同的时间保存不同的数据类型和不同长度的变量。因此,联合类型也称为共用体。 和结构体的区别 结构体和联合体的主要区别是:结构体内部数据时分开放的,而联合体所有数据是放在一个地址空间内,我们只能使用其中一个数据。
11.1.1. 定义联合类型
union 联合名 { 数据类型 成员名; 数据类型 成员名; 。。。 数据类型 成员名; };联合变量名
11.1.2. 定义联合变量
例如 union u_tag { char cval; int ival; float fval; }u; // 或者 union u_tag u;也可以
11.1.3. 联合变量成员的引用
#include <stdio.h> //头文件 #include <stdlib.h> int main() { union ThreeInOne //定义联合并声明联合变量 { int a; char b; float c; }Test; Test.a = 8; //使用成员变量a printf("Test.a=%d\n", Test.a); Test.c = 6.7; //使用成员变量c printf("Test.c=%f\n", Test.c); Test.b = 'A'; //使用成员变量b printf("Test.b=%c\n", Test.b); printf("Test.a=%d\n", Test.a); //意义 return 0; }
11.1.4. 联合变量数组
#include <stdio.h> //头文件 #include <stdlib.h> int main() { union ThreeInOne //定义联合并声明联合变量 { int a; char b; float c; }Test[3]; Test[0].a = 8; Test[1].b = 'g'; Test[2].c = 3.14; printf("Test[0].a=%d\n", Test[0].a); printf("Test[0].a=%c\n", Test[1].b); printf("Test[0].a=%f\n", Test[2].c); return 0; } //控制台输出 //Test[0].a = 8 //Test[0].a = g //Test[0].a = 3.140000
11.1.5. 联合变量指针
#include <stdio.h> //头文件 #include <stdlib.h> int main() { int i = 10; float f = 3.7; char c = 'A'; union ThreeInOne { int a; char b; float c; }*u; u = &i; printf("u->a=%d\n", u->a); u = &f; printf("u->b=%f\n", u->c); u = &c; printf("u->c=%c\n", u->b); }
11.1.6. 结构中嵌套联合类型
11.1.7. 结构和联合的区别
对于由多个不同数据类型成员组成的结构变量和联合变量,在任何同一时刻,结构的所有成员都存在,而联合变量中只存放了一个被选中的成员。 结构变量的不同成员赋值是互不影响的,而对于联合变量的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了。 从数据存储角度来说,结构和联合变量的区别主要在于联合变量的成员占用同一个内存空间,而结构变量中的成员分别独占自己的内存空间,互相不干扰, 
11.2. 枚举
注意: 枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值 只能把枚举元素赋予枚举变量,不能把枚举元素的数值直接赋予枚举变量 枚举元素不是字符常量也不是字符串常量,使用时不要加单双引号 初始化赋值时,可以为其赋负数,然后成员扔按依次加一的规则确定其值
11.2.1. 定义枚举类型
enum 枚举名 { 标识符[=整型常熟], 标识符[=整型常熟], 标识符[=整型常熟], 。。。 }枚举变量; 如果没有加入整型常数部分,将按初始化值来,第一个是0,第二个是1.....
11.2.2. 定义枚举变量
#include <stdio.h> //头文件 #include <stdlib.h> int main() { enum weekday { monday, tuesday, wednesday, thursday, friday, saturday, sunday }a, b, c; a = monday; b = friday; c = sunday; printf("a=%d\nb=%d\nc=%d\n", a, b, c); return 0; }
11.3. 类型说明
typedef 原类型名 新类型名; 例如: typedef int integer; 用处: typedef struct minned { char *name; char sex; unsigned short age; float amout; }minned; 实际上就是用minned 代替了struct minned 该结构体当你使用了typedef,以后可以使用下面的形式定义结构变量 minned needs[N]; 等价于 struct minned need[N]
11.4. 二进制数据存储
11.5. 位运算
11.5.1. 位逻辑运算符
在C语言中,可对位进行4种逻辑运算,分别是: &:按位与; |:按位或; ^:按位异或; ~:取反。
1. 按位与运算
&为双目运算符 只有对应的两个二进制位均为1时,结果位才为1。只要有一个二进制位为0,运算的结果就为0。 因此,任何数与0进行按位与运算,其结果都为0。 例如 7 & 0 0000000000000111 & 0000000000000000 = 0000000000000000 作用: 使用按位与运算,可清除或保留二进制位中的某些位。例如,要把16位二进制数的高8位清0,保留低8位,可使用以下运算,如图所示。 
2. 按位或运算
按位或运算符“|”是双目运算符。 将运算符两侧的数对应的二进制位进行或运算。只有对应的两个二进制位均为0时,结果位才为0,只要有一个二进制位为1,运算的结果就为1。例如,表达式 例如 7 | 10 000000000000111 | 000000000001010 = 000000000001111 |=的作用就是掩模 经典位运算 将tmp左移1位,最后一位为0 然后通过按位或运算,flag=0000 0001 可以将1放入tmp的最后一位,其他位不动 tmp = tmp << 1; //等价于tmp<<=1 tmp |= flag;
3. 按位异或运算
按位异或运算符“^”是双目运算符。将运算符两侧的数对应的二进制位进行异或运算。只有对应的两个二进制位不相同时,结果位为1,若对应的两个二进制位相同,结果位为0。 例如, 7^10 0000000000000111 ^ 0000000000001010 = 0000000000001101
4. 求反运算
求反运算符“~”是单目运算符,具有右结合性。 将参与运算的数的各二进位按位求反,原来为0的转换为1,原来为1的转换为0。 例如: ~0000000000000111 =1111111111111000 在对二进制取反运算的时候,并不会区分符号位 也会将符号位一起进行取反运算 因此在用~时,尽量使用无符号数
11.5.2. 移位运算符
左移运算
左移运算符为“<<”,是一个是双目运算符。 将左侧运算符的二进制位按右侧指定的数值进行移位,移位的方向是向左移动,每移动一位,原最左侧的二进制位自动丢弃,低位补0。 例如,将整数7向左移1位的过程,如图 移位前 0000 0000 0000 0111 移位后 0000 0000 0000 1110 所有位都向左移动1位时,原来位于最高位的0被移除变量内存空间而丢弃,尾部以0补充,得到的结果为14,相当于将原来的数被乘以2 例如 10左移1位 0000 0000 0000 1010 10 0000 0000 0001 0100 20 左移1位 0000 0000 0010 1000 40 左移2位
右移运算
右移运算符为“>>”,与左移运算符类似,不同的是,该操作符当操作数向右移位, 最右侧的位被丢弃,左侧最高位补0。 例如,将整数7向右移1位的过程, 0000 0000 0000 0111 7 0000 0000 0000 0011 3 相当于将原来的数被除以2
11.5.3. 位运算的复合赋值运算符

11.5.4. 位运算的用途
判断某一位或某几位上的数是否为1 其中mask是掩码 #include <stdio.h> //头文件 #include <stdlib.h> int main() { unsigned short r,i; //声明并初始化 unsigned short mask=2; printf("请输入一个整数:"); scanf("%d",&i); //输入整数 r=i & mask; if(r==mask) //判断输出 printf("整数%d的第3位二进制位为1。\n",i); else printf("整数%d的第3位二进制位不为1。\n",i); system("pause"); return 0; } 同样的,使用位运算符还可以设置某位为0 在单片机中运用较多,可设置寄存器某位为0
11.5.5. 32单片机寄存器实战
bit18.bit19位清零
GPIOF_MODER&= ~(3<<(2*9)) 3 0000 0000 0000 0000 0000 0000 0000 0011 3<<(2*9) 0000 0000 0000 1100 0000 0000 0000 0000 ~(3<<(2*9)) 1111 1111 1111 0011 1111 1111 1111 1111 GPIOF_MODER&= ~(3<<(2*9))=>GPIOF_MODER=GPIOF_MODER&~(3<<(2*9)) XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX & 1111 1111 1111 0011 1111 1111 1111 1111 = XXXX XXXX XXXX 00XX XXXX XXXX XXXX XXXX 将bit18 bit19位清零
bit18 bit19位置配置01
GPIOF_MODER |= 1<<(2*9) 1 0000 0000 0000 0000 0000 0000 0000 0001 1<<(2*9) 0000 0000 0000 0100 0000 0000 0000 0000 GPIOF_MODER |= 1<<(2*9) =》 GPIOF_MODER = GPIOF_MODER | 1<<(2*9) xxxx xxxx xxxx 00xx xxxx xxxx xxxx xxxx | 0000 0000 0000 0100 0000 0000 0000 0000 = xxxx xxxx xxxx 01xx xxxx xxxx xxxx xxxx
11.6. 位域
12. 预处理
12.1. 预处理简介
12.1.1. 预处理功能
预编译的主要作用如下: 将源文件中以“#include”格式包含的文件复制到编译的源文件中。 用实际值替换用“#define”定义的字符串。 根据“#if”后面的条件决定需要编译的代码。 预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕后自动进入对源程序的编译。
12.1.2. 预处理命令
ANSI C标准提供了多种预处理命令,如宏定义、文件包含、条件编译等,具体如下所示。 #define:宏定义。 #error:强迫编译程序停止编译,并输出错误信息。主要用于程序调试。 #include:使编译程序将另一源文件嵌入到带有#include的源文件中。 #if…#elif…#else…#endif:预编译分支命令。与C语言的if…else if…else…语句类似,根据#if或#elif后面的常量表达式决定编译哪个分支的代码。 #ifdef:如果后面的常量已经被定义,则返回true,编译其后的代码。 #ifndef:如果后面的常量未定义,则返回true,编译其后的代码。 #undef:撤销已定义的宏名。 #line:改变预定义宏_LINE_与_FILE_的内容。 #pragma:使用该预处理命令可向编译程序传送各种指令,编译程序可设置编译器支持C99标准。 按照使用的范围,这些预处理命令可以分为如下几大类,在后面几节中将分别对其进行介绍。 文件包含指令:#include; 宏定义指令:#define、#undef; 条件编译指令:#if、#else、#ifdef、#ifndef、#endif; 其他编译指令:#line、#error、#pragma。
12.2. 宏定义命令
宏定义指令是用一些标识符作为宏名来代替一些符号或常量的命令。 在前面各章的程序中,其实曾多次使用#define命令定义符号常量。使用#define命令并不是真正地定义符号常量,而是定义一个可替换的宏。 被定义为宏的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串替换,称为“宏代换”或“宏展开”。 在C语言中,宏分为有参数和无参数两种。下面分别介绍这两种宏的定义和调用。
12.2.1. 无参数的宏定义
其定义格式如下: #define宏名 字符串 #:表示这是一条预处理命令(凡是以“#”开头的均为预处理命令)。 define:关键字“define”为宏定义命令 宏名:是一个标识符,必须符合C语言标识符的规定,一般以大写字母表示宏名。 字符串:可以是常数、表达式、格式串等。在前面使用的符号常量的定义就是一种无参宏定义。 典型的宏定义指令示例如下: #define PI 3.14
无参数宏定义示例
#include <stdio.h> //头文件 #include <stdlib.h> #define PI 3.14 //宏定义 int main() { float s,r=2.0; //声明并初始化 s=PI*r*r; //计算面积 printf("圆的面积s=%.2f\n",s); //输出 printf("PI的值:%.2f\n",PI); system("pause"); return 0; }
12.2.2. 带参数的宏定义
带参宏定义的一般形式为: #define宏名(形参表) 字符串 #define FUN(A,B) 2*A*B y=FUN(3,5); 首先宏定义了FUN,在程序中使用FUN时,将括号中的实参3和5分别替换形参A和B,即该表达式等同于下面的表达式 y=2*3*5; 在定义带参宏时,宏名和形参之间不能有空格出现; 否则,会将宏定义成为无参形式,而导致程序出错。 例如,以下的宏求一个数的绝对值: #define ABS(x) (x)<0?-(x):(x) #define ABS (x) (x)<0?-(x):(x) 以上定义中,宏名ABS和形参(x)之间有空格,在调宏替换时将会出错
12.2.3. 带参数的宏定义
带参宏定义的一般形式为: #define宏名(形参表) 字符串 #define FUN(A,B) 2*A*B y=FUN(3,5); 首先宏定义了FUN,在程序中使用FUN时,将括号中的实参3和5分别替换形参A和B,即该表达式等同于下面的表达式 y=2*3*5; 在定义带参宏时,宏名和形参之间不能有空格出现; 否则,会将宏定义成为无参形式,而导致程序出错。 例如,以下的宏求一个数的绝对值: #define ABS(x) (x)<0?-(x):(x) #define ABS (x) (x)<0?-(x):(x) 以上定义中,宏名ABS和形参(x)之间有空格,在调宏替换时将会出错
12.2.4. #undef命令
#undef命令用于取消前面已定义过的宏名。一般形式如下: #undef宏名 例如: #define MAX 100 ⋯ # undef MAX
举例说明#undef命令的应用方法
#include <stdio.h> //头文件 #include <stdlib.h> #define COUNT 25 //宏定义 int main() { printf("COUNT =%d\n", COUNT); //输出COUNT =10 #undef COUNT //撤销宏定义 //printf("COUNT =%d\n", COUNT); //此时再引用是错误的 system("pause"); return 0; }
12.2.5. 预处理操作符#和##
操作符#
操作符##
与操作符#类似,操作符##也可用来在带参宏中替换部分内容。 该操作符将宏中的两个部分内容连接成一个内容。例如,定义如下宏: #define VAR(n) v##n
12.3. 文件包含指令
其中#include为文件包含指令,双引号或尖括号括起来的是读入的源文件。 典型的文件包含指令示例如下: #include "myfile.h" #include <math.h> define MATH_FILE "C\keil\inc\math1.h" #include MATH_FILE 在使用#include命令时,要注意以下几点。 [插图] 在C语言程序中,一个#include命令只能包含一个文件。 [插图] #include命令出现在哪,被包含的文件就在哪里引入。一般来说,被包含的文件要放在包含文件的头部。 [插图] 被读入的源文件用双引号括起来时,表示先从源文件所在目录下搜索,如果未发现文件,再到系统指定的目录下搜索。 [插图] 被读入的源文件用尖括号括起来时,表示直接到系统指定的目录下搜索,这种方式一般用于包含库文件的头函数。例如#include<math.h>。 [插图] 当文件为自定义文件时,编译器通常先搜索当前目录,其次按#include指定的目录去检索。 [插图] 如果文件标识符中包含文件路径,则仅在指定路径中搜索被嵌入文件。
12.4. 条件编译指令
13. 补充
13.1. C语言fgetc和fputc函数用法详解
13.1.1. 字符读取函数 fgetc
用法
fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符。fgetc() 的用法为: int fgetc (FILE *fp); fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。 EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1。fgetc() 的返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)。 EOF 不绝对是 -1,也可以是其他负数,这要看编译器的实现。
fgetc() 的用法举例:
char ch; FILE *fp =fopen("D:\\demo.txt","r+"); ch =fgetc(fp); 表示从D:\\demo.txt文件中读取一个字符,并保存到变量 ch 中。 在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。 注意:这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。
13.1.2. 字符写入函数 fputc
用法
fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符。fputc() 的用法为: int fputc ( int ch, FILE *fp ); ch 为要写入的字符,fp 为文件指针。fputc() 写入成功时返回写入的字符,失败时返回 EOF,返回值类型为 int 也是为了容纳这个负数。例如: fputc('a', fp); 或者: char ch = 'a'; fputc(ch, fp); 表示把字符 'a' 写入fp所指向的文件中。
14. 主题