一.字符输入输出函数
1. putchar
从内存向终端输出一个字符。其一般形式为 putchar(c),c既可以是字符型变量或整型变量。
putchar('A');
putchar(65);
输出AA
2. getchar
从终端向内存输出一个字符。getchar函数没有参数,其一般形式为 getchar(),函数的值就是从输入设备得到的字符。
int ret=getchar();
putchar(ret);
printf("%d,%c\n",ret,ret);
输入 A
输出 A65,A
putchar与getchar可结合使用
putchar(getchar()-32);
实现将终端输入的小写字母转换为大写输出
3. printf
printf的一般形式为:printf(格式控制,输出表列),当二者数量不匹配时多余位随机输出
整型:
1. %u 以十进制无符号整型输出
2. %lu 以十进制无符号长整型输出
%ld 以十进制长整型输出
由于int与char型兼容,%c也可输出整型,如输出int时会将高位截断,保留低八位转换为对应的ASCII码值,虽结果有误,但语法正确
浮点型:
3. %f 以float型输出,也可以输出double
%lf 以double型输出
八进制:
4. %o 以八进制输出
%#o 以0xxx输出八进制
十六进制:
5.%x 以十六进制输出
%#x 以0x输出十六进制(小写)
%#X 以0X输出十六进制
科学计数法:
6. %e
%E
' \" ' 输出 "
域宽输出:
7. %nd 以n位域宽输出,若数据大于n位则以实际位数输出,否则左侧补空
%-nd 以n位域宽输出左对齐
%0nd 以n位域宽输出并且前面补0
%.nf 以n位小数输出
%m.nf 以m位域宽,n位小数输出
%-0nd 报警告只会左对齐不补零
八进制与十六进制也可指定输出域宽,如%8o表示输出八位八进制数,%8x表示输出八位十六进制数;此外,%lo与%lx则可表示以八和十六进制输出long型。
八进制和十六进制输出无关符号
int a = -1;
printf("十进制(有符号): %d\n", a); // 输出:-1
printf("八进制(无符号): %o\n", a); // 输出:37777777777(32位系统)或1777777777777777777777(64位)
printf("十六进制(无符号): %x\n", a); // 输出:ffffffff(32位系统)或 ffffffffffffffff(64位)
总结如下图所示
4. scanf
将从终端输入的值存入已开辟的内存地址中。
1.格式说明


在输入整型时,可以使用空格,Tab与Enter隔开两数,除此之外用其他字符则会直接中断输入,舍弃后续读入的数字。
scanf("%d %d",&m,&n);
scanf("%d%d",&m,&n);
这两种写法相同,同时若输入小数,则只会保存整数部分
在%c输入字符时, 中间加空格可吸收所有空格,Tab与Enter。
scanf("%c %c",&a,&b);//可吸收空格,Tab与Enter
scanf("%c%c",&a,&b);//读取空格,Tab与Enter,因为其也是字符
2.注意事项
此外 ,%f 不能接收double型,且双引号里写了什么,在输入时也要相同,所以除了格式控制符之外尽可能少写其他数据。以下是使用scanf时应注意的事项。


此外,当有多次scanf输入时,在终端会连续读取
int a;
char ch;
scanf("%d",&a);
//getchar();
scanf("%c",&ch);
当输入一个整型之后会继续读取下一行的字符输入,可使用getchar()吸收空格等,注意getchar只能读取一个字符
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
//通过循环可实现持续吸收缓存
5. puts/gets
puts 将字符串从内存输出到终端,自动换行
char str[128]={"hello,world"};
puts(str);
gets 将字符串从终端读到内存(字符串数组),可能造成越界访问。
char str[128]={0};
gets(str);
6. strlen
strlen(str):计算字符串的长度(字符型数组中有效字符个数,不包含'\0')
其一般形式为:
strlen(字符数组)。它是测试字符串长度的函数。函数的值为字符串中的实际长度(不包括'\0'在内)。例如:char str[10]={"China"}; printf("%d",strlen(str));输出结果不是10,也不是6,而是5。
sizeof(str):求字符型数组在内存中所占的字节数(和空间开辟时大小有关)。
char str1[]={"hol"}; printf("%ld\n",strlen(str1)); //3 printf("%ld\n",sizeof(str1)); //4 注意,strlen只能读取以\0为结尾的字符串,若定义char str1[]={'q','t'}此类,则不会输出2,而是 会继续向t后面的内存查找,直到找到以\0为结尾的字符位置;而若定义char str1[]={'q','t','\0'}则会 输出2。注意,strlen不能用于整型数组,且不能传二维字符型数组,只能传一维数组
char str[2][2]={0}; strlen(str); //错误 strlen(str[0]); //正确
7. strcpy
strcpy(str1,str2):字符串拷贝
其一般形式为:strcpy(字符数组1,字符串2)
strcpy是stringcopy(字符串复制)的简写。它是“字符串复制函数”,作用是将字符串2复制到字符数组1中去。注意:
(1)字符数组1必须定义得足够大,以便容纳被复制的宇符串。字符数组1的长度不应小于字符串2的长度。
(2)“字符数组1”必须写成数组名形式(如strl),“字符串2”可以是字符数组名,也可以是一个字符串常量。例如:strcpy(strl,"China")。
(3)如果在复制前未对strl数组赋值,则strl各字节中的内容是无法预知的,复制时将str2中的字符串和其后的’0'一起复制到字符数组1中,取代字符数组1中的前面字符,最后几个字符并不一定是斜杠零,而是strl中原有的字节的内容。strncpy(str1,str2,n)将str2的前n个字符复制到str1中替换,要注意替换的字符长度不应超过str1的大小。
8. strcat
strcat(str1,str2):字符串拼接
其一般形式为:strcat(字符数组1,字符数组2)
strcat是String Catenate(字符串连接)的缩写。其作用是连接两个字符数组中的字符串,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值即字符数组1的地址。strncat(str1,str2,n)将str2的前n个字符拼接到str1末尾。
9. strcmp
strcmp(str1,str2):字符串比较
返回int型数据,若str1>str2,返回大于0的值;若str1==str2,返回0;;若str1<str2,返回小于零的值。
同样的,strncmp(str1,str2,n)比较str1和str2前n个字符大小,返回结果同上
二.函数的定义
1.降低程序的耦合性(关联度),减少重复代码
2.让程序模块化,增强代码的复用性。
返回值 函数名(形参表)
{函数体;
}
返回值:类型说明符:int、char...等基本数据类型、构造数据类型、void。返回函数最终结果 注意,可以不接收返回值,编译器会自动丢弃
void:空类型,当函数执行完,不需要有任何结果返回时,可以使用void类型的返回值
函数名:标识符,遵循标识符的命名规则
见名知意
Strcpy---》string copy
动宾--- get_max_val();print_star();
show_menu();形参表:函数执行过程中需要用到的条件
形式:
数据类型 形参名,数据类型 形参名,数据类型 形参名
三.函数的调用与声明
函数名(实参表);
实参表:传递给函数的具体的条件值1,值2,值3,...
1.实参的个数和形参的个数必须一致
2.实参和形参数据类型要一致,或者要相兼容
3.实参和形参名字可以相同,也可以不相同4.函数不被调用则不执行,调用几次,则执行几次
函数名(实参表);------>表达式的返回结果是函数的返回值类型
当函数的返回值有一个明确的返回类型时必须使用return返回,否则返回随机值,void可不用
函数的声明:
若被调函数在主调函数之后定义,则应在文件头部进行函数声明,如:
int add(int a,int b); //add在main函数后面定义,则此时若在main调用add函数, //则应在头部进行声明 int main() { int sum=add(4,5); } int add(int a,int b) { return a+b; }也可在主调函数内部进行声明,不过作用域仅限在主调函数内,编译时会报警告编译器找不到合适的原型,其他函数调用时可能报错,故不推荐此用法。由于C 编译器是从上往下顺序编译的,所以若定义在前,调用在后则无需声明,否则应进行声明。
此外,若被调函数add定义在另一个
.c文件(比如utils.c)中,而test在main.c中:
即使
utils.c里的add写在前面,main.c编译时也看不到它。此时必须在
main.c顶部或通过#include "utils.h"来声明add,否则链接时会报“未定义引用(undefined reference)”错误。总结一句话:只要被调函数的定义位置在调用位置之上,编译器就能直接识别,不需要任何额外声明。
四.函数的嵌套与传参
函数嵌套:
保护现场:当函数要被调用的时候,会将函数调用位置的下一条指令地址保存在栈空间;
恢复现场:当被调函数执行完时,通过出栈保护现场的地址,决定cpu继续执行的位置在函数被调用的下一条指令位置。
函数传参:
1.函数传参过程,其实是实参向形参拷贝的过程,实参有实参的空间,形参有形参的空间(值传递)。
2.值传递:在被调函数中,无法修改主调函数中的变量。
3.函数传参的顺序:自右向左
由图可知,主函数的实参与被调函数的形参是两个独立的空间。
五.函数的递归调用
在调用一个函数的过程中又直接或间接的调用该函数本身称为递归。
本质:循环方式
注意:1.递归要有递归的出口,否则会成为死循环
2.递归系统调用比循环大(少用)
递归思想:跳出循环,抓住问题最根本,从循环结束条件开始入手。
六.函数和一维整型数组
传参方式:
定义:
返回值 函数名( int 数组名 [ ],int len)
{ //sizeof(数组名) ---》sizeof(int*)---》8B // 任何指针都占8B
//外面数组怎么使用,函数内数组就怎么使用}
调用:
函数名(数组名,数组长度);
注意:
1.数组作为函数参数传递时,形参必须要是数组类型:int a[],[]中的常量可以有,也可以省略。
2.数组作为函数参数传递时,本质传递的时数组的首元素地址,所以形参本质是一个地址是数组的
起始地址。3.由于数组名传参本质传递的是首地址,所以必须要传递整形数组的长度。
4.数组作为函数参数传递给被调函数,函数内部的使用方法和外面使用一样,除了sizeof(数组名)
此外,在被调用函数中声明形参数组的大小是不起任何作用的,因为C语言编译对形参数组大小不做检查,只是将实参数组的首元素的地址传给形参数组。因此,形参数组名获得了实参数组的首元素的地址,前已说明,数组名代表数组的首元素的地址,因此,形参数组首元素和实参数组首元素具有同一地址,它们共占同一存储单元,故而数组元素的值也相同。
七.函数和一维字符型数组
传参:
char 数组名[100];
定义:
返回值 函数名(char 数组名[ ])
{函数体;
}
调用:函数名(数组名);void print_str()
print_str(str);
注意:
1.一维字符型数组作为函数参数传递时,只需要传递数组即可,不需要传递长度,因为字符型型数组保存的字符串,未尾有'0'结束标志。
2.数组作为函数参数传递时,本质传递的是数组的首元素地址(在被调函数族:sizeof(数组名形参)-------》本质求地址在计算机内存中的大小)。
八.函数和二维整型数组
传参:
定义:
返回值 函数名 (int 数组名 [][列数],int 行数)
{
}
调用: 函数名 (数组名,行数)
int a[3][4] = {0};
void print_2D_array ( int a[ ][4], int hang){
}
printf_20_array( a, sizeof(a)/sizeof(a[0]) );
注意:
1.二维整形数组传参时,形参必须说明是二维数组,行数可以省略,列数不能省略。
形参: int a [ ][列数]
2.在自定义的函数中,行数无法计算,所以可以通过参数的方式传递
3.在自定义的函数中,列数可以计算:
sizeof(a[0])/ sizeof(a[0][0]);
九.函数和二维字符型数组
传参:
定义:
返回值 函数名 (char 数组名[ ][列数],int 行数)
{
}
调用: 函数名(数组名,行数);char str[3][100] = {0};
void print_2Dc_array(char str[ ][100], int hang){
}
print_2DC_array( str , 3 );
十.变量的作用域与生命周期
1.标识符作用域(空间角度)
作用域: 标识符/变量可以被使用的范围
局部作用域:在{ }内部被定义的变量的作用范围全局作用域:可使用范围在全局,0外面。比如:定义函数
局部变量:定义在局部作用域中的变量(函数内部定义的变量、函数形参)
全局变量:定义在全局作用域的变量。 //不宜过多,容易降低代码安全性
注意:
1.标识符必须先定义再使用。
2.在相同作用域中,不能定义同名标识符(变量、函数)
3.在具有包含关系的作用域中,出现了同名标识符,外层标识符在内层不可见(就近原则)
2.变量的生命周期(时间角度)
变量的生命周期:从变量的空间开辟到空间回收对应的生存期。
动态生命周期:
局部变量:在被定义的时候开辟空间,超出其作用域范围空间被回收。
静态生命周期:
全局变量:从程序运行之前空间开辟,程序运行结束空间被回收
static修饰的静态变量:1.作用域没发生变化,局部作用域
2.生命周期由动态变为静态生命周期
总结:
局部变量:
1. 作用域在局部,超出局部作用域无法访问;
2. 动态生命周期,定义时开辟空间,超出作用范围空间被回收;
3. 局部变量保存在栈区,未被初始化为随机值。
全局变量/static静态变量:
1. 全局变量作用域在全局,static静态变量作用域在局部;
2. 静态生命周期:程序运行之前空间开辟,程序运行结束空间被回收
3. 全局变量/static静态变量保存在数据区。
4. 全局变量/static静态变量未被初始化时,默认为0。
5. 未初始化的全局变量/static静态变量存储在.bss段,.bss段系统默认清0.
6. 已初始化的全局变量/static静态变量存储在.data段。

全局常量 / 静态常量(
const修饰的全局变量):存储在数据区(具体叫只读数据段 .rodata)。程序运行期间这片内存是只读的,试图修改会导致程序崩溃(Segmentation fault)。局部常量(函数内部的
const变量):存储在栈区。它本质上和普通局部变量一样存在栈里,只是编译器帮你检查,禁止你在代码里修改它。它不特殊。字符串字面量(例如
"hello"):存储在数据区(常量区)。比如char *p = "hello";,这个"hello"就放在只读数据段里。被
#define定义的宏常量:不占用存储空间。它在预处理阶段就被替换掉了,编译成机器码时,它直接变成了一条指令里的立即数(立即数),存在代码段(Text Segment)里。
代码区(.text):存放你的程序指令(CPU要读的机器码),只读。
数据区(Data Segment)——:
.data 段(已初始化数据区):存放已经初始化且值不为0的全局变量和静态变量。比如
int a = 10;放在这。.bss 段(未初始化数据区):存放未初始化或初始化为0的全局变量和静态变量。比如
int b;或static int c=0;放在这。注意:它不占磁盘空间,加载时才分配。.rodata 段(只读数据区):这就是你问的“字符串常量区”。比如
char *p = "hello";里的"hello"就放在这里。它也是数据区的一部分,但被标记为只读(Read-Only)。
3.变量的存储类型
int a;
存储类型 数据类型 变量名;
auto:默认存储类型
static:静态变量
1.static修饰局部变量,静态变量,具备静态生命周期(延长局部变量的生命周期)。
2.static修饰函数,该函数只允许在当前文件内使用。
3.static修饰全局变量,该变量只允许在当前文件使用。
register:寄存器变量
register修饰变量时,表示建议编译器将变量存储到寄存器,用于频繁使用的变量,CPU访问速度更快。
register int i=0;//register只是个建议 int *p; p=&i; //❌地址是对内存的编号,i是寄存器变量,无法取地址。
extern:修饰全局变量/函数声明
1.extern修饰全局变量,用来声明全局变量,表示允许被外部的.c文件使用。
2.extern声明函数,表示该函数允许被外部的.c文件使用。
十一.静态函数与普通函数的区别
| 特性 | 静态函数(static) | 普通函数(默认 extern) |
|---|---|---|
| 可见范围 | 仅限定义它的 .c 文件 | 整个项目(所有 .c 文件) |
| 链接属性 | 内部链接(Internal Linkage) | 外部链接(External Linkage) |
| 命名冲突风险 | ✅ 安全(同名也不怕) | ❌ 危险(同名会链接报错) |
| 封装性 | ✅ 强(对外隐藏实现细节) | ❌ 弱(暴露给全局) |
是否能被 extern 引用 | ❌ 不能(链接器找不到) | ✅ 能(通过头文件声明即可) |
| 常见使用场景 | 模块内部辅助函数、工具函数 | 模块对外公开的 API 接口 |
十二.预处理命令
1.gcc编译的四个步骤
gcc main.c -> a.out
预处理:
展开和#相关的代码:展开头文件、展开宏、处理预处理命令
#include <stdio.h> #define PI 3.1415 #if0 #endifgcc -E main.c -o main.i
编译:
将c语言程序转换成汇编指令。
gcc -S main.i -o main.s
汇编:
将汇编指令转换成二进制可执行程序
gcc -c main.s -o main.o
链接:
负责链接系统的函数/库函数的实现(链接函数)
gcc main.o -o main
2.预处理命令
- 宏定义
- 不带参宏:--->符号常量
#define 标识符 值
#define PI 3.1415
注意:
1.宏作为预处理命令,负责在预处理阶段展开,不做运算,只是代码的替换
2.宏标识符最好大写
3.宏只是代码的替换,尽量加()
4.宏定义不是C语句,不必在行末加分号。如果加了分号则会连分号一起进行置换
5.#define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。通常,#define命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。
6.可以用#undef 命令终止宏定义的作用域。7.宏定义是专门用于预处理命令的一个专用名词,它与定义变量的含义不同,只作
字符替换,不分配内存空间
- 带参宏
#define 宏名(参数表) 字符串
1.对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替#define命令行中的形参
2.在宏定义时,在宏名与带参数的括号之间不应加空格;否则将空格以后的字符都作为代替字符串的一部分
- 带参宏与函数的区别:
(1)函数调用时,先求出实参表达式的值,然后代人形参。而使用带参数的宏只是进行简单的字符替换。例如上面的S(a+b),在宏展开时并不求a+b的值,而只将实参字符"a+b"代替形参r。
(2)函数调用是在程序运行时处理的,为形参分配临时的内存单元。而宏展开则是在编译前进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值"的概念。
(3)对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换。而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时代入指定的字符串即可。宏定义时,字符串可以是任何类型的数据。
(4)调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
(5)使用宏次数多时,宏展开后源程序变长,因为每展开一次都使程序增长,而函数调用不会使源程序变长。
(6)宏替换不占运行时间,只占编译时间。而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。一般用宏来代表简短的表达式比较合适。有些问题,用宏和函数都可以。
(7)宏是预处理阶段的代码替换,会使代码体积增大.函数是在运行时的调用,需要保护和恢复现场
- 头文件包含
#include<stdio.h> #include "head.h"头文件放:
1.可以有包含的其他头文件
2.声明的数据类型
3.宏定义
4.函数和全局变量的声明
注意:
1.<>表示包含的是系统中的头文件,让编译器从系统路径下找:/usr/include
2." "表示包含的头文件是用户自己定义的头文件,让编译器从当前路径下寻找。
esc进入命令行,:+vsp竖向打开多文件;:+sp横向打开多文件
#ifndef __HEAD_H__ //防止宏定义被多次展开
#define __HEAD_H__
#include<stdio.h>
#define ADD(A,B) ((A)+(B))
#endif

- 条件编译
1.头文件中防止头文件重复包含
#ifndef 宏 #define 宏 //头文件内容 # endif根据“宏”有没有被定义出来决定该头文件中的内容要不要加入到编译中。
2.条件编译
#if 逻辑结果表达式 // 代码 # endif根据“逻辑结果表达式”的结果,决定中间的代码要不要加入到编译中
//代码屏蔽
# if 0 //代码 # endif
3.
#ifdef 宏 //代码1 # else //代码2 # endif根据“宏”有没有被定义决定是否编译代码1或者代码2
#ifndef 宏 //代码1 # else //代码2 # endif




894



被折叠的 条评论
为什么被折叠?



