环境搭建
- windows 安装 Visual Studio编辑器,即可编译执行c代码
- mac 安装Xcode编辑器,即可编译执行c代码
- 如果不使用集成编辑器,则需要再电脑中单独安装gcc软件
- 使用gcc测试c语言文件举例:
- touch hello.c //新创建一个c语言文件后,输入相关c代码
- gcc hello.c //使用gcc把c代码编译为可执行文件,会生成一个a.out文件
- 也可以一次编译多个文件,示例:gcc xxx1.c xxx2.c
- gcc h* //编译所有以h开头的.c文件
- ./a.out //执行生成后的文件
概述
- gcc语编译命令/大致过程
- 预编译:
- gcc -E xxx.c
- 文件包含、宏定义
- 把头文件和源文件编译到一个文件中
- 编译:
- gcc -c xxx.c
- 把c的代码翻译成机器指令 gcc -c
- 会生成xxx.o文件,注意并不是最终的可执行文件
- 链接:
- gcc xxx.o -o xxx2
- 将xxx.o编译为可执行文件,-o xxx2:指定编译后的文件名
- gcc会把标准库、第三方库中的函数和你的程序整合在一起
- c语言加载过程
- 程序一开始存储在磁盘上(数据和指令)
- 运行的时候加载到内存中:数据区、代码区、堆、栈
- main函数
- 主函数,操作系统直接调用,一个程序只能有一个main函数
- c语言不直接支持二进制的输入输出
数据类型
概述
- 字符集
- c/c++语言字符集由字符、数字、空格、标点和特殊字符组成
- 标识符
- 程序员在程序中定义的单词,它表示程序中的一些实体,如:变量名、函数名、对象名
- 标识符由字符、数字、下划线组成
- 第一个字符必须是字母或下划线
- 大小写敏感
常用的数据类型
- 基本类型
- 整型
- 短整型 short
- 整型(默认) int
- 长整型 long
- 字符型 char
- 实型
- 单精度型 float
- 双精度型(默认) double
- 构造类型
- 数组 - 相同类型的数据集合
- 结构体 struct - 不同类型的数据集合
- 共用型/联合体 union
- 枚举型 enum
- 指针类型
- 空类型 void
- 定义类型 typedef
基本数据类型
- 整型
- 整型的值可以是正的、负的或者是0,但必须是整数,负数在内存中是以二进制的补码形式存储的
- 整型存储在计算机中占32位,最高位为符号位
- long类型在64位系统下是8个字节,在32位系统下是4个字节
关键字 |
所占子节数 |
数的表示范围 |
int |
4 |
-2{31} ~ 2{31} - 1 |
[signed] short [int] |
2 |
-2{15} ~ 2{15} - 1 |
[signed] long [int] |
4/8 |
-2{31} ~ 2{31} - 1 |
unsigned int |
4 |
0 ~ 2{32} - 1 |
unsigned short [int] |
2 |
0 ~ 2{16} - 1 |
unsigned long [int] |
4/8 |
0 ~ 2{32} - 1 |
- 实型
- 实型也称作浮点型
- c语言中浮点数包括float和double
- 系统的默认类型是双精度浮点型double,在使用单精度浮点型float时,需要在数字后面添加f或F用以区分
关键字 |
所占子节数 |
数的表示范围 |
精确表示的数字个数 |
float |
4 |
绝对值E-37 ~ E+38 |
7 ~ 8 |
double |
8 |
绝对值E-307 ~ E+308 |
16 ~ 17 |
- 字符型
- 字符型用于存储字母和标点等字符
- 字符在计算机中采用二进制的ASCII码来存储
- char类型存储一个字符占用一个字节的存储空间
关键字 |
所占子节数 |
数的表示范围 |
[signed] char |
1 |
-128 ~ 127 |
unsigned char |
1 |
0 ~ 255 |
- sizeof运算
- sizeof是一个单目运算符,返回变量或类型的子节长度,以子节为单位
- 一般格式为:sizeof(<数据类型>)或sizeof(<变量名>)或sizeof(<常量>)
char ss[10] = "hello";
char * sp = "hello";
printf("%d\n", sizeof(ss));
printf("%d\n", sizeof(sp));
输出、输入
- 格式化输出
- 可以使用printf()将数据显示在屏幕上,几种基本数据类型的数据符号如下:
- %d 以十进制方式输出有符号的整数int或short(short也可以用%hd输出), %2d表示在输出时该数字占用两个位置
- %u 输出无符号整数
- %o 以八进制方式输出整数
- %x 以十六进制方式输出整数
- %lu 以long型输出
- %ld 输出无符号长整型
- %f 以小数形式输出float
- %lf 以小数形式输出double
- %.lf 以指定精度的形式输出double,例如:%.5lf 即显示小数点后5位
- %c 以单个字符形式输出char
- %p 以指针形式输出字符型数据
- %s 以字符串形式输出字符型数据
- %e 以指数(科学计数法)形式输出字符型数据
- 因在printf中方%有特殊含义,所以要输出%时需要写两个%,如:printf("%%\n");
- printf("%03d\n", 1)//打印时占3个位置,不足3位的在前面用0补齐
- printf("%.3d\n", 1)//打印时占3个位置,不足3位的在后面用0补齐
printf("int = %lu\n", sizeof(long));
char c = 'A';
printf("c = %c, c = %d\n", c,c);
printf("%.2f\n", 1.234);
- 标准输入
- scanf
- 在VS编辑器环境中有的函数加上_s更安全,比如:scanf_s
- perror
int num = 0;
scanf("%d", &num);
常量
- 常量也称字面量,内存空间是只读模式
- 常量分类
- c语言中整型常量分:十进制、八进制数(前面加0)、十六进制数(前面加0x)
printf("%d, %d, %d\n", 123, 0123, 0x123);
- 实型常量-有两种方式表示
- 十进制形式、由符号、数字和小数点组成
- 指数形式,即科学表示法,由整数(或小数)、e(或E)、整数顺序组成,e(E)之前必须有数字
- 如:1234e-5 表示1234 * 10{-5}, 1234e+5 表示1234 * 10{+5}
- 字符常量
- 计算机用ASCII码的形式来保存字符,其中65表示大写字符 'A'
- c语言中,单引号中的表示字符常量,其中单引号是必不可少的
- 换行符为 '\n'
- 字符串常量
- 字符串是用双引号括起来的字符序列
- c语言中规定以字符 '\0'作为字符串的结束标志。在输出时不显示,只用来表示字符串的结束
- 例如: "hello"在内存中的存储情况为:(hello\0)
- 注意: 字符串 "1" 和字符 '1' 在内存中存储分别为 (1\0) (1)
- 两个字符的数学运算,是安装其对应的ASCII码值来计算的,如 '1'+'0'
- 技巧:把字符数字转成相应的整数数字,减字符 '0'即可,整数数字转字符时,加上字符 '0'即可
printf("%d\n", '1'+'0');
printf("%d\n", '1'-'0');
printf("%c\n", 1+'0');
- 转义字符
- 以反斜线开头
- \0 空字符
- \n 换行符,换下一行
- \r 回车符,回到下一行的开头
- \t 水平制表符
- \b 退格符
- \' 单引号
- \" 双引号
- \\ 反斜线
- 枚举型常量
- 枚举类型的成员都是常量
- 在枚举类型中,每个枚举常量代表一个整型值,默认从0开始,用户可以给枚举常量进行赋值
enum weekday { SUN, MON }
enum weekday { SUN=1, MON }
- 符号常量 - 宏定义
- 可以使用宏表示常量。
- 例如:#define PI 3.1415926
- #define是一个预编译指令,所有的预编译指令都是以#开头的
- 行尾不能有分号
- define前要有#号
- 符号常量名最好使用大写
- 零值
- c语言中可以用以下三种方法表示零值
- 整数0
- 字符串结束符 '\0'
- 空值NULL,通常用于指针操作,在c++中false也代表0值
变量
- 变量的定义
- 取值可变的量,内存空间是可读可写的
- 变量的定义形式:(类型说明符 变量名标识符, 变量名标识符...)
- 如:int num; long num2,num2;
- 注意:根据编码规范,一行只允许定义一个变量,不推荐一行定义多个变量
- 定义变量的要求
- 必须以 ';'号结尾
- 变量定义必须放在变量使用之前
- 变量的初始化和赋值
- 初始化就是在变量定义时给一个初始值
- 变量被定义后,如果不给它初始值,那么它的值是一个随机值
- 非同文件中,相互使用变量时,需要声明,或在xxx.h头文件中声明
- 例如:extern int num;//extern可以省略
- 普通的全局变量
- 在函数外部定义,作用范围是程序的所有地方
- 生命周期:程序运行的整个过程一直存在
- 默认值0
- 静态全局变量
- 前面用 static 修饰
- 只在当前文件有效,作用范围是程序的所有地方
- 默认值为0
- 生命周期:程序运行的整个过程一直存在
- 普通的局部变量
- 作用范围只在当前代码块中有效
- 生命周期:调用函数时才为其开辟空间,函数结束即释放
- 默认值是随机的
- 静态局部变量
- 前面用 static 修饰
- 作用范围只在当前代码块中有效
- 生命周期:第一次调用函数时,为其开辟空间,函数结束不释放
- 默认值0
运算符和表达式
基本概念
- 运算符
- 算术运算符:+ - * / % ++ --
- 关系运算符:< <= == > >= !=
- 逻辑运算符:! && ||
- 位操作运算符:<< >> ~ | ^ &
- 赋值运算符:= 及其扩展
- 条件运算符:?:
- 逗号运算符:,
- 指针运算符:* &
- 求字节数运算符:sizeof
- 特殊运算符:() [] -> .
- 表达式
- 表达式是由运算符、操作数和标点符号组成的
- 表达式可以是一个单独的常量或变量
- 表达式是有值的
- 可以为表达式添加括号,称为表达式的嵌套使用
- 运算符的优先级
- 优先级相同时,按运算符结合性规定的方向处理
- 从高到低:括号、增减量、指针、正负、逻辑非、算数、关系、逻辑、条件、赋值、逗号
- 综合性分为两种
- 左结合性(自左至右),例如算数运算符
- 右结合性(自右至左),例如赋值运算符
基本运算符
- 赋值运算符
- 当赋值号两边类型不一致时,根据将右边类型按照左边类型转换
- 在c语言中规定,任何表达式在末尾加上分号就构成语句
- 算术运算符
- 两个整数相除得,会把结果取整,最终得到一个整数
- 取余运算:
- 余数符号和被除数相同
- 不允许对浮点数取余,没有意义
- 自增、自减运算
int n = 0;
int n1 = n++;
int n2 = ++n;
- 关系运算符
- 关系运算符可以直接应用于基本数据类型,对于浮点数不要轻易直接用 '=='和 '!='比较大小
- 逻辑运算符: &&与、||或、!非
- 条件运算符:如:三目运算(1?2:3)
- 位运算:
- 按位与& 按位或|
- 右移>> 低位移除,高位(逻辑右移补0,算术右移补符号位)
- 左移<< 高位移除,低位补0
- 计算机系统二进制中,左侧是高位,右侧是低位
- 逗号运算符:,
类型转换
- 隐式类型转换
- 隐式类型转换是由编译器完成的
- c语言规定转换规则是由低级向高级转换
- 赋值运算时,如果两边类型不同,将自动转换为和左边相同的类型
- 当表达式中只有char short int类型时,全部成员将转成int类型参与运算
- 当表达式中出现带小数点的实数时,全部成员将转成double类型参与运算
- 当表达式中即有有符号的数,也有无符号的数时,将全部转成无符号的数参与运算
- 类型转换都是临时转换,并不改变变量本身的类型
- 显示类型转换
- 又叫强制类型转换
- 直接在要转换的数据前用括号加需要强制的类型
- 转换浮点数时,会直接丢弃小数点后面的数据,如:int(1.6) //得1
逻辑控制语句
- 程序中语句的分类
- 表达式语句
- 函数调用语句:由函数名,实际参数加上;号组成
- 空语句
- 程序中最简单的语句,只有一个单独的分号
- 可以用作空循环体
- 复合语句:由一个或多个括在括号内的语句组成
- 控制语句
- 分支语句:if、switch
- 循环语句:do while、while、for
- 辅助控制语句:break、goto、continue、return
分支语句
- if语句
- if(){}else{}
- if(){}else if(){}else{}
- switch语句
switch(表达式)
{
case 常量表达式1:
语句1;
break;
case 常量表达式2:
语句2;
case 常量表达式3:
语句3;
break;
default:
break;
}
循环
- 循环语句概述
- while循环语句
- for语句
- do while循环
- 辅助控制语句 break、goto、continue、return
- 嵌套循环语句
printf("aa\n");
goto gotoName;
printf("bb\n");
gotoName:
printf("cc\n");
数组
- 数组定义
- 数据类型 数组名[常数表达式]
- 定长数组:常量表达式的值必须在编译时已知,可以是整型常数或是const int值,也可以是常量表达式
- 可以用宏指定数组的大小
- 变长数组:可以不指定数组长度,或者长度可变
- 数组名(常量)代表数组中第一个元素的地址,也是是数组所占内存块的首地址。在二维数组中数组名代表第一行的地址
- 可以用sizeof(数组名)获取数组在内存中所占的字节长度
- 初始化数据时,必须连续初始化,不能间隔空值
- 如果不想给数组初始化值,可以用0清空数据
#define SIZE 5
int arr[SIZE];
int arr2[2] = {1,2};
printf("%d\n", arr[3])
int arr3[] = {1,2,3}
int arr4[2] = {};
arr4[0] = 1;
int arr5[SIZE] = {};
for(int i=0;i<SIZE;i++){
printf("请输入一个整数:\n");
scanf("%d", &arr5[i]);
}
int arrRow[m][n];
int arrRow2[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int arrRow3[3][3] = {1,2,3,4,5,6,7,8,9};
int arrRow4[][3] = {1,2,3,4};
- 一维数组
- 多维数组
- 定义二维数组时可以省略第一维的值,编译器会根据元素的总个数进行分配空间
- 不管是几维数组,在内存中都是按一行存储的
字符串
字符串定义
- 字符串是位于双引号中的字符序列,在内存中实际上是字符数组
- 在内存中以 '\0'结束所占字符比实际多一个
- 字符数组中,字符串的字符存放在相邻的存储单元,每个字符占一个单元
- 可以用memset清零字符数组,记得加头文件#include<string.h>
- 定义字符数组时,应确保数组长度比字符串长度至少多1
- 未被使用的元素均被自动初始化为0
- 被初始化的字符数组可以省略长度,默认为字符串长度加1
char carr[] = {'a','b','c'};
char carr2[] = "abc";
字符串的输入和输出
- 格式化输入
- 可以用scanf()接收字符串,它不接受空格,格式:scanf("%[n]s",字符数组名); n可指定要接收的字符串长度
- 用scanf()数组字符串时,字符数组名前不加&
- 其他输入函数
- 输入字符串函数gets(),它可以接收空格,以回车结束输入。
- 不安全的函数,容易造成字符数组越界
- 对应有 puts()输出
- 输入字符函数
- getchar() 输入回显
- getch() 输入不回显,可以接收 '\r',常用于输入密码操作,需要#include<conio.h>
- 对应有 putchar() 输出字符
char carr[] = "";
char str[10] = {0};
printf("请输入字符串\n");
gets(str);
puts(str);
putchar(str[0])
putchar('\n');
char str1[] = "how are you?";
char str2[] = "I'm fine.";
str2[9] = ' ';
puts(str2);
字符串操作函数
- 需要#include<string.h>
- size_t代表无符号整型,他是在库头文件中用typedef定义出来的
- size_t strlen(const char * s);
- 统计字符串长度,返回字符串中的字符个数
- 检查 '\0',没遇到就加1,遇到就结束(返回值不含 '\0')
- 内存赋值函数 void * memset(void * ptr, int value, size_t num);
- 也称空间设定函数
- 将ptr指向的内存空间的num个字节全部赋值为value
- 返回目的内存的首地址,即ptr的值
- 字符串拷贝 char * strcpy(char * dest, const char * src);
- 格式: strcpy(目的str1, 源str2);/需要保证str1空间够用(比str2大),否则不安全,造成内存污染
- 目标字符串必须是变量,不能是常量
- '\0'也会被拷贝进去
- 字符串拷贝 char * strncpy(char * dest, const char * src, size_t n);
- 不检查目标字符串的大小,当目标字符串内存不足时,会导致崩溃
- n:指定拷贝的字符个数
- 不拷贝 '\0'
- 如果n大于src指向的字符串中字符的个数,则在后面填充n-strlen(src)个'\0'
- 字符串比较 int strcmp(const char * s1, const char * s2);
- int res = strcmp(str1, str2)
- 比较相同位置上的ASCII码值的大小(常用于比较字符串是否相等)
- 相等返回0,str1大于str2返回正数1,str1小于str2返回负数-1
- 字符串比较 int strncmp(const char * s1, const char * s2, size_t n);
- 比较两个字符串的前n个字符
- 比较相同位置上的ASCII码值的大小
- 相等返回0,s1大于s2返回正数1,s1小于s2返回负数-1
- 字符串拼接 char * strcat(char * dest, const char * src);
- 先删除dest后的串标志'\0',把src中的字符连接到dest的后面,src的'\0'也会拼接进去
- 返回值是dest的首地址
- 字符串拼接 char * strncat(char * dest, const char * src, size_t n);
- 追加str指向的字符串的前n个字符,到dest字符串的后面
- 会追加 '\0'
- 如果n大于src的字符个数,不回补多余的 '\0'
- 字符串查找 char * strchr(const char * s, int c);
- 字符匹配
- 在字符串中找ASCII码为c的字符,可用于检测是否包含某个字符
- 注意是首次匹配,返回找到的字符的地址,找不到返回NULL
- 字符串查找 char * strrchr(const char * s, int c);
- 字符匹配
- 在字符串中找ASCII码为c的字符,可用于检测是否包含某个字符
- 注意是末尾匹配,返回找到的字符的地址,找不到返回NULL
- 字符串匹配函数 char * strstr(const char * haystack, const char * needle)
- 在haystack指向的字符串中查找needle指向的字符串
- 首次匹配,返回找到的字符串的地址,找不到返回NULL
- 字符串切割 char * strtok(char * str,const char * delim);
- 按照delim指向的字符串中的字符,切割str指向的字符串,即在str指向的字符串中匹配delim中的字符将其变成'\0'
- 调用一次切割一次,想继续切割时再次调用的时候第一个参数传NULL表示接着上次切割的位置继续切割
- 遵循首次匹配原则
- 返回切割后的字符串地址,如果未匹配上表示已经不能再用该字符切割了,则返回NULL
- 需要#include<stdlib.h>
- 字符串转换函数,
- 转成浮点型 double atof(const char * nptr);
- 转整型 int atoi(const char * nptr);
- 转long型 long atol(const char * nptr);
- 需要#include<stdio.h>
- 格式化输出 int sprintf(char * buf, const char * format,...);
- 输出到buf指定的内存区域
- 例:sprintf(str, "%d", 123);//利用该函数,可以把数字123转成字符串赋值给str变量
- int sscanf(char * buf, const char * format,...);
- 默认以空格作为分隔符
- 从buf指定的区域读取数据
- 跳过数据语法:%*s、%*d
- 指定宽度语法:%[字节个数]s
- 集合操作语法(只支持获取字符串) %[类正则语法]
- 例:%[a-z] 表示匹配a到z的任意字符,尽可能多的匹配,遇到不在a-z的字符即停止获取
- 需要#include<ctype.h>
- 字符测试函数,
- 判断字符是否是数字 isalnum
- 判断字符是否是字符 isalpha
char str[] = "98876j54s3x2t";
memset(str, '\0', 5);
memset(str, 'A', 5);
memset(str+5, 'A', 5);
strcpy(str, "bb");
char * sp = "abcdefg";
char * p = strchr(sp, 'd');
if(p != NULL){ printf("sp 中 包含 'd' 字符"); }
char s1[100] = "abc&&def&&g";
char s2[100] = "&&";
char * p = strstr(s1, s2);
if(p != NULL){
printf("p-s1 %ld\n", p - s1);
}
char str[100] = "aaa;:bb,:;.cc.,dd";
char * p[10];
printf("%s\n", str);
p[0] = strtok(str, ":;.,");
printf("p0 = %s\n", p[0]);
printf("%s\n", str);
while(p[i] != NULL){
i++;
p[i] = strtok(NULL, ":;.,");
}
for(int j = 0; j < i; j++){
printf("p[%d] = %s\n", j, p[j]);
}
char ss[100];
sprintf(ss, "%d-%d-%d", 2023,1,2);
printf("%s\n", ss);
sscanf("2023-01-02", "%d-%2d-%2d", &a, &b, &c);
printf("%d %02d %02d\n", a, b, c);
char ss2[32];
sscanf("123 456", "%*d%s", ss2);
sscanf("12345678","%2s", ss2);
sscanf("123a45b6c78","%[^ac]", ss2);
sscanf("ddd12&name=zhangsan&age=18","%*[^&]&%[^&]", ss2);
函数
函数的基本概念
- 函数的定义:把一些功能相同的程序分割成一个个程序块,封装起来
- 函数不能嵌套定义
- 允许递归调用自己本身
- 外部函数
- 内部函数
- 返回值类型前加 static 修饰的函数
- 只在当前文件中有效
函数 的定义和参数
- 无参函数
- 有参函数
- 函数的定义说明
- 函数名首字母必须大写,每个单词的首字母也大写,最好用下划线分割
- 函数的声明和定义可以分开
- 先声明再使用
- 函数可以被多次重复声明,但是只能定义一次
- 函数的参数
- 分为形参和实参
- 实参出现在主调函数中,进入被调函数后,实参变量不能使用
- 形参变量只有在被调用时才分配内存单元
- 实参和形参占据不同的存储单元
- 函数默认采用值传递
- 实参可以是常量、变量、表达式、函数,但是在传递前,必须有确定的值
- 函数的返回值
- 函数返回值类型和函数定义时的返回值类型应保持一致,如果不一致,则以函数定义时为准,自动进行类型转换
- 无返回值的函数,应定义返回值为空类型,如果不写返回值类型默认为int
char[] str = "abc";
char str[] = "abc";
char* str = "abc";
函数的应用
- 程序的内存区域
- 代码区:存放程序的代码和各个函数的代码块
- 数据区:存放程序的全局变量和静态变量、常量(不包括define定义的数据)
- 栈区:存放程序的局部变量,内存小,连续存放,先进后出。在一个变量前加上static后,这个变量将不占用栈内存空间,会放在数据区
- 堆区:存放动态数据,需要用指针访问,内存大,不连续
- 局部变量
- 局部变量也称内部变量,在函数内部使用,不能被该函数外的代码使用
- 函数调用结束后,局部变量所占的内存自动释放
- 局部变量应尽量定义在程序的开头
- 如不初始化,它的值将是随机的
- 全局变量
- 对整个程序都是可见的
- 它不属于某个函数,而属于整个源文件
- 一般在main()函数之前声明全局变量
- 全局变量有效区域就是从它定义开始到文件结束
- 如不初始化,系统自动初始化为0
- 静态局部变量
- 在局部变量前加static
- 只被初始化一次,在第一次进入该函数时创建,退出函数时保留其值
- 静态局部变量和全局变量一样,系统会默认初始化为0
- 函数与数组
- 数组名作为实参传递:由于数组名就是数组的首地址,所以这实际上是地址传递,即修改形参数组中的内容将相应的改变实参数组中的内容
#include<stdio.h>
extern int global;
int main(){ }
static void Hello(){};
long l = sizeof(arr)/sizeof(arr[0])
void print_arr(int arr[]){
printf("size = %lu\n", sizeof(arr)/sizeof(arr[0]));
}
函数递归调用
const 关键字
- const 修饰函数参数
- const 修饰函数返回值
- 对于指针类型的返回值,如不希望通过返回的指针修改该指针指向的变量值,需要在返回值前加const修饰
- 修饰普通变量:只读模式
指针
指针的概念
- 指针就是地址, 可以认为就是内存中存放地址编号的容器/变量
- 指针变量:存放变量地址的变量,书上或程序中所说的指针一般指的是指针变量,指针类型的变量占4个字节
- 对应类型的指针变量,只能存放对应类型的变量地址。如:整型指针变量只能整型变量地址,但是通用指针(void *)可以保存任意类型的变量地址
- 扩展:
- 字符变量(char ch = 'a'),ch占一个字节只有一个地址编号,这个编号就是ch的地址
- 整型变量(int n = 0x12345678),n占4个字节占有4个存储单元,有4个地址编号。规定第一个编号代表n的地址
- 使用指针的目的:通过指针能够找到被值的变量,或者说要通过指针间接访问到被值的变量
- 变量的值和变量的地址-指针运算符
- 地址运算符:& 取地址
- 间接运算符 * 取值
- 后面跟一个指针名称或地址时,给出存储在被指向地址中的数值
- 示例:
- int* p = &bank;//将bank变量的地址给p
- *p = bank//将bank的值放入p地址所对应的内存中,*p等价于p指向的变量
- 指针类型
- NULL
- 空指针,哪里也不指向,可以认为其指向内存编号为0的存储单位
- 一般用于给指针初始化,如:int * np = NULL;
int num = 0;
int* p = #
printf("*p = %d, p = %p\n", *p, p);
*p = 2;
printf("num = %d\n", num);
int n2 = 0x12345678;
char* p2 = (char*)&n2;
printf("%0x\n", *p2);
指针变量的定义
- 一般形式为:类型*指针变量名
- 一个*号代表一个指针
- 一个类型的指针只能指向同类型的变量
- 指针也可以被声明为全局、静态、局部
- 指针变量的初始化
- 指针变量使用之前必须赋予具体的值,并且只能赋地址
- 如果一个指针在定义后没有初始化,应该给它赋予一个空值,避免出现使用未被初始化的指针引起的系统混乱
- 指针变量的引用
- 通过指针间接访问变量,使用*来表示取地址的内容
- void类型的指针,可以指向任何的变量,但是在使用时,需要进行强制类型转换后才能使用
- 所有的指针大小(地址编号 ),在64位系统中都是8字节,在32位系统中都是4字节
指针运算
- 指针与整数的加减运算
- pnid +/- n <===> pnid +/- n*sizeof(指针指向类型)
- 指针和指针的减法运算
- 指针和指针之间只有减法运算,没有其他运算
- pnid1 - pnid2 = (pnid1 - pnid2) / sizeof(指针指向类型)
- 两个指向同一个数组元素的指针,相减的结果是两个指针中间相差的元素个数
- 指针之间的比较运算
- 其实就是指针地址值的比较
- 相同类型的指针指向同一个数组元素时,指向前面元素的指针小于指向后面元素的指针
- 根据指针类型不同在运算时,移动的字节数也不同
- 如:int* p = &n; 在自增运算时,每次p++以4个字节为单位向前移动
指向数组元素的指针
- 数组名访问数组元素
- 数组名(是一个常量)保存的是数组首元素的地址编号,其实就是一个指向该数组中第一个元素的指针
- 在实际的数组元素前写一个 '&'符
- 写一个表达式,在数组名后面加元素偏移量
- 用指向数组的指针访问数组元素
- 如果指针变量p已指向数组中的一个元素,则p+1指向数组中下一个元素
int arr[5] = {1,2,3,4,5};
int *p = arr;
int n1 = *(arr);
int n2 = *(p++);
printf("n1=%d, n2=%d\n", n1, n2);
int arr2[2][3] = {1,2,3, 4,5,6};
int (*p)[2][3] = &arr2;
字符指针和字符串指针
- 指针字符串的表示形式:char *pStr = "hello";
- 注意pStr指向的是字符串变量的首地址
- 指针形式的字符串,指向的内容是不可变的,但可以重新赋值
- 字符数组形式的字符串,不能直接重新赋值
- 字符串指针变量与字符数组
- 字符数组和字符指针都可以存储和运输字符串
- 数组名是常量,存放的是以 '\0'结束的字符串;指针名是变量,存放的是字符串的首地址
char str[32] = "language";
strcpy(str, "hello word");
char * ps = &str;
char * pstr = "aa";
char * pstr1 = "language";
pstr1 = "hello world";
char *p2;
char * pstr2 = (char*)malloc(10);
strcpy(pstr2, "hello");
const 指针
- const int* xxx 和 int const* xxx 两者意义是相同的
- 可以指向const对象,也可以指向非const的对象
- 不能通过间接符*,修改const指针所指向的对象的内容
- 可以重新赋值新的地址,但不可修改指针指向的内容
- int * const xxx
- 这种指针必须初始化
- 指针本身的地址不可修改,但是它指向的内容可以修改
- const int * const xxx
- 这种指针综合了上述两种指针的特性,既不可以更改所指向的地址,也不可以修改所指向的内容
const int SIZE = 5;
int * const p2 = &SIZE;
*p2 = 10;
printf("size = %d, *p2 = %d\n", SIZE, *p2);
指针的指针
- 定义:指针在内存中也有地址编号,存放指针地址的指针变量称为指针的指针
- 对指针取地址得到的就是一个指针的指针
int n = 1;
int * p = &n;
int * * pp = &p;
printf("%d\n", **pp);
void swap(char * * ppp){
*ppp = "world";
}
int main(){
char * p = "hello";
swap(&p);
printf("p = %s\n", p);
return 0;
}
指针数组
- 指针数组是数组元素都是(相同类型)指针变量的特殊数组
int n = 10;
int* parr[5] = {&n};
char * names[3] = {"aa","bb","cc"};
数组指针 - 指向一维数组的指针
- 指向数组的指针定义如下:类型名称 (*指针名)[数组长度]
- 数组指针的长度不能省略
- 数组长度、元素类型必须与指针定义时给出的长度、类型相同
- 指针指向的是整个数组,而不是数组元素
int (*p)[5];
int a[3] = {1,2,3};
int (*arrP)[3] = &a;
指针与二维数组
- 数组指针注意配合二维数组来使用
- 在二维数组中,数组名其实就是一个数组指针,因为它存储的是二维中第0个一维数组的地址
- 访问二维数组的指针有三种形式
- 指向普通元素的指针
- 指向一维数组的指针
- 指向二维数组的指针
- 数组指针运算:指针+1相当于指针+1*sizeof(数组名),即跳到二维中的下一个一维数组
- 实际应用中:一维数组指针常配合二维数组使用,二维数组指针常配合三维数组使用...
int arrNum[2][2] = {1,2,3, 4,5,6};
int *p = &arrNum[0][0];
int (*p2)[3] = &arrNum[0];
int (*p3)[2][3] = &arrNum;
int a[10];
int a2[4][5];
- 数组名和指针的区别
- 相同点
- 数组名保存的是数组首地址,指针保存的也是地址
- 可以把数组名直接赋值给一个指针变量,这样这个指针就指向了该数组的首地址
- 不同点
- 数组名是常量,指针是变量
- 对数组名取地址得到的是数组指针,对指针取地址得到的是指针的指针
- 数组指针取*
- 数组指针取*,并不是取值的意思,而是指针的类型发生了变化
- 一维数组指针取*,结果为它指向的一维数组第0个元素的地址,它们还是指向同一个地方
- 二维数组指针取*,结果为一维数组指针,它们还是指向同一个地方
- 三维数组指针取*,结果为二维数组指针,它们还是指向同一个地方
- ...多维以此类推
函数指针
- c语言规定,函数的名字就是函数的首地址,即函数的入口地址
- 定义函数指针:数据类型(*指针变量名)(形式参数列表)
- 把函数首地址赋给指针变量时,直接写函数名称即可,不用写括号和函数参数,这个变量就称为函数指针变量
- 函数指针其实就是保存函数地址的变量,方便在其他地方调用该函数
- 利用指针变量调用函数时,要写明函数的实际参数
float(*p1)(int x);
float *p2(int x);
int add(int a, int b);
int sub(int a, int b);
int main(){
int (*p)(int,int) = add;
printf("res = %d\n", p(10, 20));
p = sub;
printf("res = %d\n", (*p)(10, 20));
typedef int (*funcPointer)(int, int);
funcPointer funArr[2] = {add, sub};
for(int i = 0; i < 2; i++){
printf("arr-res = %d\n", funArr[i](20, 10));
}
return 0;
}
- 函数指针数组:由若干个相同类型的函数指针变量组成的,在内存中按顺序存储的数组
void fn1(int x, int y){}
void fn2(int x, int y){}
void (*pfa[2])(int,int) = {fn1, fn2};
指针变量作为函数参数
- 指针变量以实参的形式传递给函数,可以在函数中改变实参的值
- 指针参数,传递实参时应该传递变量地址
- 数组名可以作函数的实参和形参
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
void testArr(int * p){
*(p +1) = 5;
}
void testArr2(int (*p)[2]){
p[0][1] = 100;
}
void testFn(char ** p){
p[0] = "cc";
}
int main(){
int x = 10;
int y = 20;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y);
int arr[3] = {1,2,3};
testArr(arr);
printf("arr[1] = %d\n", arr[1]);
int arr2[2][2] = {
{1,2},
{3,4}
};
testArr2(arr2);
printf("arr2[0][1] = %d\n", arr2[0][1]);
char * strs[2] = {"aa", "bb"};
testFn(strs);
printf("strs[0] = %s\n", strs[0]);
return 0;
}
指针函数
- 返回值是一个指针的函数,称为指针型函数
- 不可以返回局部变量的地址,会报警告,可返回局部静态变量的地址
char* testFn(){
char * s2 = "aa";
return s2;
}
int main(){
char * ss = "aa";
ss = testFn();
printf("ss = %s\n", ss);
return 0;
}
api
- system
- 可通过system方法执行系统shell命令
- 如:system("cls") 清空屏幕
stdlib.h
- 随机数
- srand((unsigned int)time(NULL))
- 以当前时间为准,设置随机种子
- 如果不设置随机数种子,每次获取的随机数将都是一样的
- rand()
time.h corecrt.h
- time(NULL)
- 获取当前时间戳,单位秒
- time_t st = time(NULL);//时间戳是long long类型的数据,所以需要用corecrt中的time_t类型
conio.h
关键字/修饰符
- signed 有符号的
- unsigned 无符号的
- register 用于修饰的变量是寄存器变量
- static 静态的
- const 常量、只读
- auto 自动的;
- 修饰的变量,是具有自动存储器的局部变量,可以省略;
- 进行类型推导,即根据变量的初始化表达式自动推断变量的类型
- extern 外部的,一般用于函数或全局变量的声明
- typedef 给一个已有的类型,重新起个类型名
- volatile 用volatile定义的变量是易改变的,即高速cpu每次用该变量时都要从内存中取不要使用寄存器中的备份,保证是最新的值
变量的存储类别
内存的分区
- 内存:物理内存、虚拟内存
- 操作系统会在物理内存和虚拟内存之间做映射
- 写应用程序时,看到的都是虚拟内存
- 在程序运行时,操作系统会将虚拟内存进行分区
- 堆:动态申请内存时,会在堆里开辟空间
- 栈:主要存放局部变量
- 静态全局区
- 代码区:存放程序代码
- 文字常量区:存放常量
预处理、动态库、静态库
c语言编译过程
- 预处理/预编译 gcc -E hello.c -o hello.i
- 将.c中的头文件展开,宏展开,宏替换
- 生成.i文件
- 不检查语法
- 编译 gcc -S hello.i -o hello.s
- 汇编 gcc -c hello.s -o hello.o
- 链接 gcc hello.o [可同时链接多个文件] -o xxx
预处理种类
- #include
- #include<> 在系统指定的路径下找头文件。(linux系统头文件默认路径:/usr/include,库文件默认路径:/usr/lib)
- #include"" 依次在 当前目录、系统指定路径 下找头文件
- include也可以用于包含.c文件,但是不建议这样做,因为include包含的文件会在预编译时被展开,如果同一个.c文件被包含多次,就会被展开多次,容易导致函数的重复定义
- #define
- 宏定义,结尾不需要加分号
- 作用范围:从定义的地方到本文件末尾
- #undef 终止宏
- #pragma
- 该指令用于指定计算机或操作系统特定的编译功能
- 如:#pragma warning(disable:4996)
- 在c文件开始处写上这句话即告诉编译器忽略4996警告
- 在VS编辑器中使用strcpy、scanf等一些不安全的标准c库函数会报4996错误,可以用该方法屏蔽
#define PI 3.14
#define S(a,b) a*b
- 选择性编译
- #ifdef xxx #else #endif
- #ifndef xxx #else #endif
- #if 表达式 #else #endif
- 只有当表达式的值为真或表达式的计算结果为真时才认为有效,否则走else逻辑
#ifndef __XXX__
#define __XXX__
extern inf fun();
#endif
静态库
- 动态编译
- gcc hello.c -o hello
- 使用的时动态库文件,只建立链接关系,在运行时动态地加载库文件的内容
- 静态编译
- gcc -static hello.c -o hello
- 使用的是静态库文件,会将库文件内容打包到可执行文件中,编译后体积较大
- linux系统下制作静态库
main函数
int main(int argc, char* argv[]){
printf("argc = %d\n", argc);
for(int i = 0; i < argc; i++){
printf("argv[%d] = %s\n", argv[i]);
}
}
动态内存分配
- 静态内存分配
- 在程序编译或运行过程中,按事先规定大小分配内存空间。如:int n[10];
- 分配在栈区或全局变量区,一般以数组的形式呈现
- 动态内存分配
- 在程序运行过程中,根据所需大小自由分配空间
- 分配在堆区,一般使用特定的函数进行分配
动态分配函数
- stdlib.h 依赖库
- void * malloc(unsigned int size);
- 在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型,函数原型返回void*指针,使用时必须做相应的强制类型转换
- 申请的内存空间内容是随机的,不确定的,一般使用memset初始化
- 返回值:
- 成功,返回分配空间的起始地址,这块空间可以认为是指定长度的数组
- 失败返回NULL
- 如果多次用malloc申请内存,第1次和第2次申请的内存不一定是连续的
- void free(void * ptr)
- 释放ptr指向的内存(必须是malloc/calloc/realloc动态申请的内存)
- free之后ptr依然指向原先的内存,但是已经不能再用了,ptr变成了野指针
int main(int argc, char* argv[]){
printf("请输入要申请的内存个数\n");
int n;
scanf("%d", &n);
int * p = (int*)malloc(n * 4);
if(p != NULL){
for(int i = 0; i < n; i++){
p[i] = i;
}
for(int j = 0; j < n; j++){
printf("%d ", p[j]);
}
printf("\n");
free(p);
} else {
printf("error");
}
}
-
-
- void * calloc(size_t nmemb, size_t size);
- size_t是无符号整型,他是在库头文件中用typedef定义出来的
- 功能:在内存中,申请nmemb块,每块的大小为size个字节的连续区域
- 申请的内存中默认的内容为0
- 返回值:
- void * realloc(void * s, unsigned int newsize);
- 重新申请内存:在圆形s指向的内存基础上重新申请内存,新的内存的大小为newsize个字节
- 如果原先内存后面有足够大的空间,就追加,如果后面的内存不够用,则realloc函数会在堆区找一个newsize个字节大小的内存申请,将原先内存中的内存拷贝过来,然后释放原先的内存,最后返回新内存的首地址
- 如果newsize比原先的内存小,则会释放原先内存的后面的存储空间,只留前面的newsize个字节
- malloc/calloc/realloc动态申请的内存,只有在free或程序结束的时候才释放
char * p = (char*)calloc(3,10);
p = (char*)realloc(p, 50)
- 内存泄露
- 申请的内存,首地址丢了,找不到了,再也没法使用了,也没法释放了,这块内存就被泄露了
char * p = (char*)malloc(10);
p = "hello";
void fn(){
char * p = (char*)malloc(10);
}
int main(){
fn();
fn();
return 0;
}
结构体
- 是一种构造数据类型,它是由相同或不同类型的数据构成的集合
- 使用结构体之前必须先有类型,然后根据类型定义结构体变量
- 定义结构体类型格式:struct 类型名 { 成员列表 };
- 定义结构体变量:struct 类型名 变量名;
- 结构体变量初始化时,必须按成员顺序初始化
- 结构体变量的使用
- 变量名.成员名
- 结构体变量的大小是 >= 其所有成员之和
- 相同类型的结构体变量,可以相互赋值
struct stu2 {
char name[20];
int age;
} lisi;
struct stu2 wangwu;
struct {
char name[20];
int age;
} zhaoliu;
struct stu {
char name[20];
int age;
};
struct stu zhangsan = { "aa", 18 };
typedef struct stu3 {
char name[20];
int age;
char * address;
} STU3;
STU3 xiaoming;
xiaoming.age = 18;
strcpy(xiaoming.name, "aa");
xiaoming.address = "bb";
struct date {
int year;
int month;
};
struct stu {
char name[20];
struct date birthday;
};
int main(){
struct stu boy = { "aa", { 2023, 1 } };
printf("%d\n", boy.birthday.year);
return 0;
}
结构体数组
- 由若干个相同类型的结构体变量构成的集合
- 定义格式:struct 结构体类型名 数组名[元素个数];
struct stu {
char name[20];
};
int main(){
struct stu boys[2] = {
{ "aa" }, { "bb" }
};
printf("%s\n", boys[0].name);
return 0;
}
结构体指针
- 存放结构体的起始地址的变量,即结构体指针变量
- 定义格式:struct 结构体类型名 * 指针变量名
- 结构体变量的地址编号和结构体首个成员的地址编号内容相同,但是指针类型不同
- 结构体数组的地址就是结构体数组中第0个元素的地址
struct stu {
char name[20];
};
int main(){
struct stu boy = { "bb" };
struct stu * p = &boy;
printf("%s\n", (*p).name);
printf("%s\n", p->name);
return 0;
}
结构体内存分配
- 规则1:给结构体变量分配内存时,会去结构体中找基本类型的成员,哪个基本类型的成员占到字节数多,就以它的大小为单位开辟内存,double类型的例外
- char 1字节
- short int 2字节
- int float 4字节
- double 在vc6.0和VS编辑器中以8字节为单位,在Linux的gcc环境中以4字节为单位
- 指针 4字节
- 成员中出现数组时,按多个变量处理
- 内存中存储结构体成员的时候,是按定义时的顺序存储的
- 规则2:字节对齐
- char 1字节对齐,即存放char型的变量,内存单位的编号是1的倍数即可
- short int 2字节对齐,即存放short int型的变量,其实内存单元的编号是2的倍数即可
- int 4字节对齐,即存放int型的变量,起始内存单元的编号是4的倍数即可
- long int 在32位平台下4字节对齐,即存放long int型的变量,起始内存单元的编号是4的倍数即可
- float 4字节对齐,即存放float型的变量,起始内存单元的编号是4的倍数即可
- double 在vc6.0和VS编辑器中以8字节对齐,在Linux的gcc环境中以4字节对齐
- 成员中出现数组时,按多个变量处理
- 开辟内存时,从上向按成员顺序依次开辟空间
- 为什么要有字节对齐
- 指定对齐原则
- 使用#pragma pack改变默认对齐原则
- 格式:#pragma pack(指定对齐值)
- 指定对齐值只能是2的n次方,如:1 2 4 8等
- 指定对齐值和数据类型对齐值相比,取较小值
位段
- 在结构体中以位为单位的成员,咱们称之为位段/位域
- 不能对位段成员取地址,因为它可能不够一个字节
- 一个字节(k)是8位(bit)
- 32位系统即 32bit 4k,即读取数据时是每次按 4k 为单位来读取的
- 64位系统即 64bit 8k,即读取数据时是每次按 8k 为单位来读取的
- 位段成员的类型只能是整型或字符型
- 一个位段成员必须存放在一个存储单元中,不能夸两个单元,如一个存储单元不能容纳下一个位段,则舍弃剩余空间,跳到下一个存储单元存储
- 位段存储单元
- char 型位段 1字节
- short int 型位段 2字节
- int 型位段 4字节
- long int 型位段 4字节
struct Stu {
unsigned int a:2;
unsigned int b:6;
unsigned int c;
unsigned :2;
};
struct Stu boy;
boy.a = 2;
struct Stu2 {
char a:1;
char b:2;
char :0;
char c:3;
};
共用体
- 共用体和结构体类似,也是一种构造类型的数据结构。在进行某些算法的时候,需要使用几种不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠。共用体所有成员占有同一段地址空间
- 定义共用体类型和结构体一样只需改一下关键字即可:union 类型名 { 成员列表 };
- 定义共用体变量:union 类型名 变量名;
- 共用体大小是其占内存长度最大的成员的大小
- 特点
- 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
- 共用体变量中起作用的成员时最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
- 共用体变量的地址和它的各成员的地址都是同一地址
- 共用体初始化时,只能为第一个成员赋值,不能给所有成员都赋初始值
枚举
- 枚举类型定义:enum 类型名 {枚举值列表};
- 枚举变量定义:enum 类型名 变量名;
- 枚举类型的成员都是常量
- 在枚举类型中,每个枚举常量代表一个整型值,默认从0开始依次编号,定义枚举类型时可以给枚举常量进行初始化值
enum week { mon, tue, wed = 5, thu, fri, sat, sun };
int main(){
enum week a = mon;
enum week b = tue;
enum week c = wed;
enum week d = thu;
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
printf("%d\n", d);
return 0;
}
文件
- 文件分类
- 磁盘文件:指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存
- 设备文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把他们的输入、输出等同于对磁盘文件等读写
- 键盘:标准输入文件
- 屏幕:标准输出文件
- 其他设备:打印机、触摸屏、摄像头、音箱等
- 在linux操作系统中,每一个外部设备都在/dev目录下对应着一个设备文件,咱们在程序中要想操作设备,就必须对与其对应的/dev下的设备文件进行操作
- 标准io库函数对磁盘文件的读取特点
- 内存(程序 数据区) -> 输出(文件缓冲区(系统/程序)) -> 磁盘(文件)
- 磁盘(文件) -> 输出(文件缓冲区(系统/程序)) -> 内存(程序 数据区)
- 全缓冲
- vs中对普通文件的读写是全缓冲的
- 标准io库函数,往普通文件读写数据的,是全缓冲的
- 刷新缓冲区的情况
- 缓冲区满了,刷新缓冲区
- 调用函数刷新缓冲区 fflush(文件指针)
- 程序结束会刷新缓冲区
磁盘文件的分类
- 一个文件通常是磁盘上一段命名的存储区,计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储
- 从用户或者操作系统使用的角度上把文件分为两类:
- 文本文件:基于字符编码的文件
- 常见编码有:ASCII、UNICODE等
- 一般可以使用文本编辑器直接打开
- 如:5678 以ASCII存储的形式为:00110101 00110110 00110111 00111000
- 二进制文件:基于值编码的文件
- 基于值比阿妹,根据具体应用,指定某个值是什么意思,一般需要自己判断或使用特定软件分析数据格式。二进制文件以 位 为单位来表示一个意思的
- 如:5678 的二进制存储形式为:00010110 00101110
- 音频文件(mp3):二进制文件
- 图片文件(bmp)文件,一个像素点由两个字节来描述*****#####&&&&& 565
- *代表红色的值R
- #代表绿色的值G
- &代表蓝色的值B
- 文本文件和二进制文件的区别
- 译码
- 文本文件编码基于字符定长,译码容易些
- 二进制文件编码是变长的,译码难一些(不同的二进制文件格式有不同的编码方式,一般需要特定的软件进行译码)
- 空间利用率
- 二进制文件用一个比特来代表一个意思(位操作),而文本文件任何一个意思至少是一个字符,所以二进制文件空间利用率高
- 可读性
- 文本文件用通用的记事本工具就几乎可以浏览所有文本文件
- 二进制文件需要一个具体的文件解码器,比如读BNP文件,必须用读图软件
- 总结
- 文件在硬盘上存储的时候,物理上都是用二进制来存储的
- io库函数对文件操作时,不管文件的编码格式(字符编码/二进制),都是按字节对文件进行读写,所以通常管文件又叫流式文件,即把文件看成一个字节流
文件指针
- 文件指针并不是指向文件地址的指针,因为文件是存放在磁盘上的,并不在内存中,它指向的是文件描述信息结构体的地址
- 文件指针在程序中用来标识一个文件的,在打开文件的时候得到文件指针,对文件指针的操作就是对文件的操作
- 定义文件指针的一般形式:
- FILE * 指针变量表示符;
- 需要 stdio.h 头文件
- FILE时系统使用typedef定义出来的有关文件的一种结构体类型,结构中含有文件名、文件状态、文件当前位置等信息
- 在缓冲文件系统中,每个被使用的文件都要在内存中开辟一块FILE类型的区域,存放与操作文件相关的信息
- 文件操作过程
- 打开文件得到文件指针
- 通过文件指针进行读写操作
- 操作后,要关闭文件
- c语言中有三个特殊的文件指针无需定义,可以直接使用
- stdin:标准输入,默认为当前终端(键盘),使用scanf、getchar函数默认从此终端获得数据
- stdout:标准输出,默认为当前终端(屏幕),printf、puts函数默认输出信息到此终端
- stderr:标准错误输出设备文件,默认为当前终端(屏幕),当程序出错时使用 perror 函数将信息打印在此终端
api
- 需要 stdio.h 头文件
- stdio.h头文件中定义了一个宏:EOF,值为-1
- fopen
- FILE * fopen(const char * path, const char * mode);
- 打开一个已经存在的文件,并返回这个文件的描述指针,或者创建一个文件,并打开此文件,然后返回文件描述指针
- 打开失败或创建失败时返回NULL
- mode 权限
- r 只读
- w 只写-文件不存在时,以指定文件名创建此文件并且打开;文件存在时清空文件内容,再打开文件
- a、a+ 追加-文件不存在则创建并打开(同w);若存在则从文件的结尾处进行写操作
- +、r+、w+ 可读可写
- rb、wb 、ab 带b的模式是以二进制形式打开文件
- fclose
- int fclose(FILE * fp);
- 关闭指定的文件,只能关闭一次,不可多次关闭同一个文件,文件关闭后不开再进行读写操作
- 成功返回0,失败返回非0的数值
- fgetc
- int fgetc(FILE * stream);
- 从stream所标识的文件中读取一个字节,将字节值返回
- 返回值
- 以t(文本)的方式:读到文件结尾返回EOF
- 以b(二进制)的方式:读到文件结尾,使用feof(文件指针)判断结尾
- feof是c语言标准库函数,其原型在stdio.h中,用于检测流上的文件结束符,如果文件结束,返回非0值,否则返回0
- fputc
- int fputc(int c, FILE * stream);
- 将c的值写到stream所代表的文件中
- 返回值
- fgets
- char * fgets(char * s, int size, FILE * stream);
- 一次读写一个字符串,碰到换行符或文件末尾则停止读取;或者读取了size-1个字节即停止读取。
- 在读取的内容后面会加一个 '\0',所谓字符串的结尾
- 成功返回目的字符数组的首地址
- 失败返回NULL
- fputs
- int fputs(const char * s, FILE * stream);
- 将指定字符串写到文件中功能
- 成功返回写入的字节数
- 失败返回EOF
- fread
- size_t fread(void * ptr, size_t size, size_t nmemb, FILE *stream);
- read函数从stream所标识的文件中读取数据,每块是size个字节,共nmemb块,存放当ptr指向的内存里
- 将文件中的数据原样读取到内存里
- 返回实际读到的块数
- fwrite
- size_t fwrite(void * ptr, size_t size, size_t nmemb, FILE * stream);
- 将ptr指向的内存里的数据,向stream标识的文件中写入数据,每块是size个字节,共nmemb块
- 例:如果ptr是有5个元素的数组,即nmemb应写为5块,size即数组中每个元素的字节数
- 将内存中的数据原样输出到文件中
- 返回实际写入的块数
- 随机读写
- 实现随机读写到关键是按要求移动位置指针,这称为文件的定位
- 完成文件定位的函数有:rewind、fseek
- rewind
- void rewind(FILE * stream)
- 把文件内部的位置指针移动到文件首部
- 文件指针经过写操作后已经到了末尾,使用rewind可将其复位
- ftell
- long ftell(文件指针)
- 获取文件流目前的读写位置
- 返回当前读写位置距离文件起始的字节数,失败返回-1
- fseek
- int fseek(FILE * stream, long offset, int whence);
- 移动文件流的读写位置
- 一般用于二进制文件,即打开文件的方式需要带b
- whence 起始位置,有三个宏
- 文件开头 SEEK_SET 0
- 文件当前位置 SEEK_CUR 1
- 文件末尾 SEEK_END 2
- offset 位移量
- 以起始点为基点
- 正数往文件的末尾方向偏移
- 负数往文件的开头方向偏移
FILE * fp = fopen("./test.txt", "rb");
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
printf("%lu\n", len);
rewind(fp);
FILE *fp = fopen('xxx.txt', "w+");
char c = 'a';
int num = 10;
fprintf(fp, "%c %d\n", c, num);
rewind(fp);
fscanf(fp, "%c %d\n", &c, &num);
fclose(fp);
常见报错信息
- segmentation fault
- 段错误
- 可能是有索引越界的情况
- 可能是栈被占满了,比如:无限递归
- Bus error
- Abort trap
- 陷阱中止
- 可能是内存溢出导致的,如:把一个长度较大的字符串拷贝到较小的字符串中
- Undefined symbols
- 未定义的符号,连接错误
- 可能是只声明了函数名,未定义函数体