C++基础入门【2】
1.指针
指针变量
定义指针变量语法格式:三种书写形式
数据类型* 指针变量名;int * pa;
数据类型 * 指针变量名; int * pa;
数据类型* 指针变量名; int * pa;
注意:数据类型问题
注意:int *pa, *pb;
int *pa,pb;
sizeof(pa) = 32位系统4字节/64位系统8字节
取地址与解引用
- 取地址,即通过取地址操作符“&”,获取其操作数变量的地址
如: int* p = &a;//p指向a - 解引用,亦称取目标,获取指向的内存值如:*p = 20;
- 注意星号“”在不同上下文中的不同语义
c = a * b ; // 将a和b相乘
int * p; //说明p是指向int类型变量的指针
*p = 20; //表示指针p所指向的目标
野指针与空指针
- 野指针,即指向不可用内存区域的指针
- 操作野指针将导致未定义的结果,构成潜在的风险。
- 产生野指针的原因
一指针变量没有被初始化
一指针变量所指向的内存已被释放 - 空指针,即值为0的指针,可用宏NULL表示
- 任何情况下,操作空指针的结果都是确定的——崩溃——操作系统保证这一点
- 空指针比野指针更适合作为指针有效性的判断依据
指针运算
指针与函数
指针与数组
- 数组名本身就是一个指针,代表数组的首元素地址
- 对数组元素进行下标访问的本质,就是对数组名和下标做指针计算的结果解引用
- arr[i]等价于*(arr+i),先算arr+i,最后解引用*(arr+i)
- &pa[0] => pa+0 => (pa+0)
- 与通常意义上的指针变量不同
– 数组名是个指针常量,不能通过再次赋值令其指向其它数据
– int a = 10;
– arr = &a;//不可以
– arr++;//不可以,目的是让它指向下一个元素
泛型指针—void*(无类型指针)
- 仅存储内存地址,不指定目标类型
- *例如: void p = &a;//通过p知道a的类型吗?
- 目标类型不确定,*不能直接解引用,p; // gcc迷茫了
- 使用前必须先做数据类型转换
- 泛型指针做指针计算,以1字节为单位
– P= Ox1000;
– P++;//0x1001
常量指针与指针常量
-
指针变量
-
- 特殊性 : 存储地址
-
- 普遍性 : 变量
-
-
指针变量
– 1.存储地址 - 4/8
– 2.变量 - 存储其他地址
– 3.变量 - 分配内存 - 有自己首地址
– 4.数据类型 - 指向的内存数据类型
– 5.*指针变量 - 获取其指向的内存数据
const型变量
- 被const关键字修饰的变量具有只读属性
- 必须在定义的同时初始化
- const int a = 520;
- a = 521; //不可以
当const作用于指针
-
常量指针
– 常量指针,指针变量保存的地址可以改变,指向的内存值不能修改
– const int* 或 int const*
– 常量指针常做为函数的输入函数,在避免值复制传递参数开销的同时,有效防止在函数中意外地修改实参 -
指针常量
– 指针常量保存的地址不能修改,内存值可以修改
– int * const
– 数组名就是指针常量
– arr++ //不可以, *(arr+i)=5;//可以修改内存 -
常量指针常量
– 常量指针常量,指针的目标和指针本身都只读
– const int * const 或 int const * const
案例1:指针强转
#include<stdio.h>
int main(void){
int a = 0x12345678;
char *p = (char *)&a;//将int类型的指针强制转换为char类型的指针,
//然后赋值给p, p保存a的首地址
printf("%p\n", &a);
printf("%#x\n", *p);//0x78
p++;//p = p + 1
printf("%#x\n", *p);//0x56
p++;//p = p + 1
printf("%#x\n", *p);//0x34
p++;//p = p + 1
printf("%#x\n", *p);//0x12
printf("--------------------------------------------\n");
//通过类型转换访问其中两个字节内存
short *p1 = (short *)&a;//将int类型的指针强制转换为short类型的指针,
//然后赋值给p1, p1保存a的首地址
printf("%#x\n", *p1);//0x5678
p1++;
printf("%#x\n", *p1);//0x1234
int *p2 = (int *)&a;
printf("%#x\n", *p2);
//获取中间两个字节
char *pp = (char *)&a;
printf("%#x\n", *pp);
pp++;
printf("%#x\n", *pp);
printf("%#x\n", *(short *)((char *)&a+1));
char *b = (char *)&a + 1;
printf("%#x\n", *b);
short *b1 = (short *)b;
printf("%#x\n", *b1);
return 0;
}
0x7ffc101160a4
0x78
0x56
0x34
0x12
--------------------------------------------
0x5678
0x1234
0x12345678
0x78
0x56
0x3456
0x56
0x3456
案例2:访问任意地址
#include <stdio.h>
int main(void){
int a = 0x12345678;
void *p = (void *)&a;//保存变量a的地址
printf("任意1字节\n");
//任意1字节
char *p1 = (char *)p;
printf("%#x\n", *p1);
p1++;
printf("%#x\n", *p1);
p1++;
printf("%#x\n", *p1);
p1++;
printf("%#x\n", *p1);
printf("------------------------------\n");
//任意1字节 - 直接访问
printf("%#x\n", *(char *)(p + 0));
printf("%#x\n", *(char *)(p + 1));
printf("%#x\n", *(char *)(p + 2));
printf("%#x\n", *(char *)(p + 3));
printf("------------------------------\n");
printf("任意2字节\n");
//任意2字节
short *p2 = (short *)p;
printf("%#x\n", *p2);
p2++;//p2 = p2 + 1 = p2 + 1 * sizeof(short) = p2 + 2
printf("%#x\n", *p2);
//任意2字节 - 直接访问
printf("------------------------------\n");
printf("%#x\n", *(short *)(p + 0));
printf("%#x\n", *(short *)(p + 2));
printf("------------------------------\n");
printf("任意4字节\n");
//任意4字节
int *p3 = (int *)p;
printf("%#x\n", *p3);
//任意4字节 - 直接访问
printf("------------------------------\n");
printf("%#x\n", *(int *)(p + 0));
printf("------------------------------\n");
return 0;
}
任意1字节
0x78
0x56
0x34
0x12
------------------------------
0x78
0x56
0x34
0x12
------------------------------
任意2字节
0x5678
0x1234
------------------------------
0x5678
0x1234
------------------------------
任意4字节
0x12345678
------------------------------
0x12345678
------------------------------
2.字符串
字符串的存储
- 字符串就是一组字符组成,用双引号括起来表示,用字符’\0’作为结束符
– “Hello,World ! \0"等价于 “Hello,World !”
– “hello”” world" 等价于 " hello world" - C语言存储字符串的方式
– 将字符串中每个字符的ASCII码按先后顺序存储在一段连续的内存中,每个字符占一个字节,最后用空字符,即ASCII码为0的字符结尾。
字符串的表示
- C语言表示字符串的方式
– 字符指针方式
– char *pstr = “hello” ; //pstr保存字符串的首地址
– 不能通过pstr修改指向的字符串,例如:pstr[1] = ‘E’
– 字符数组方式
– str[] = {‘a’ , ‘b’ ,‘c’ , ‘\0’}; //“abc”
– str[] = “abc”; -
- 数组名即字符串首地址
-
- 可以用字面值初始化,也可以用{}初始化
-
- 用{}初始化需要手动显示注明结束符,否则就是一个数组
-
- 可以任意修改
案例1:字符指针和字符串数组
#include <stdio.h>
int main(void){
printf("%s\n", "abcd\0");//打印字符串的占位符%s
printf("%s\n", "abcd");//打印字符串的占位符%s
printf("%s\n", "1234\0abcd");//1234
printf("hello,""world\n");//hello,world\n
//数据库 - 语句可以放在多行里
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n");
printf("ppppppppppppppppppppp"
"bbbbbbbbbbbbbbbbbbbbbbbbbbb\n");
//字符指针
char *pstr = "abc";
printf("*pstr = %s\n", pstr);//打印pstr指向的字符串
printf("%c %c %c\n",*(pstr+0), *(pstr+1), *(pstr+2));
printf("%c %c %c\n", pstr[0], pstr[1], pstr[2]);
printf("%hhd %hhd %hhd \n", pstr[0], pstr[1], pstr[2]);
//pstr[1]= 'a';//error
//字符串数组
char str1[] = "maab";
printf("str1 = %s\n", str1);//打印字符串
printf("str1[0] = %c\n", str1[0]);
str1[0] = 'a';
printf("str1[0] = %c\n", str1[0]);
printf("str1 = %s\n", str1);
printf("sizeof(str1) = %lu\n", sizeof(str1));
return 0;
}
abcd
abcd
1234
hello,world
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
pppppppppppppppppppppbbbbbbbbbbbbbbbbbbbbbbbbbbb
*pstr = abc
a b c
a b c
97 98 99
str1 = maab
str1[0] = m
str1[0] = a
str1 = aaab
sizeof(str1) = 5
字符串的输入和输出
- 字符串的输入
-
- scanf(“%s” , …);
-
- char *gets(char *s);
-
- 从标准输入读取一行字符串,读走换行符即返回,并将所读到的换行符置换为结尾空字符
- 字符串的输出
-
- printf(“%s”,…)
-
- int puts(const char *s);
-
- 向标准输出写入一行字符串,并追加一行换行符
字符串函数
- 标准库提供一套专门针对字符串的函数
– 字符串头文件
— #include <stdio.h>
– 字符串长度
— unsigned int strlen(const char *s)
– 字符串拷贝
— char *strcpy(char *dest,const char *src)
— char *strncpy(char *dest,const char *src,unsigned int n)
– 字符串连接
— char *strcat(char *dest,const char *src)
— char *strncat(char *dest,const char *src,unsigned int n)
字符串处理函数
strcat - string cat - 将两个字符串连接起来
将后面的字符串拼接到前面的字符串后面
strcat(数组名,数组名/字符指针/字符串字面值)
cat - 文件和终端连接起来
strcpy - string copy- 将两个字符串连接起来
将后面的字符串拼接到前面的字符串后面
strcat(数组名,数组名/字符指针/字符串字面值)
案例2:字符串拼接
#include <stdio.h>
#include <string.h>//字符串函数头文件
int main(void){
//strlen - 不包含'\0'字符的有效字符数
char *p1 = "abcdefghijklmnopqrstuvwxyz";
char str[100] = "hello";
printf("strlen(p1) = %lu\nstrlen(str) = %lu\n", strlen(p1), strlen(str));
printf("--------------------strcat-------------------\n");
//strcat
char str1[50] = "xyz";
strcat(str1, "abc");//将字符串xyz拼接到字符串abc后面,放到str1数组中
printf("str1 = %s\n", str1);
char *p2 = "mn";
strcat(str1, p2);
printf("str1 = %s\n", str1);
printf("---------------------------------------\n");
printf("str = %s\n", str);
strncat(str,"1234",2);//将字符串1234的前2个字符拼接到str数组的字符串后面
printf("str = %s\n", str);
printf("-------------------strcpy--------------------\n");
//strcpy
char str2[20] = "abcd";
printf("str2 = %s\n", str2);//abcd
strcpy(str2, "123456");//将字符串拷贝到str2数组中
printf("str2 = %s\n", str2);//123456
char *p3 = "mnxy";
strcpy(str2, p3);//mnxy \0 6 \0
printf("str2 = %s\n", str2);//mnxy
strncpy(str2, "987654123", 2);//将字符串987654123的前2个字符串拷贝到str2
printf("str2 = %s\n", str2);//12xy \0 6 \0
return 0;
}
strlen(p1) = 26
strlen(str) = 5
--------------------strcat-------------------
str1 = xyzabc
str1 = xyzabcmn
---------------------------------------
str = hello
str = hello12
-------------------strcpy--------------------
str2 = abcd
str2 = 123456
str2 = mnxy
str2 = 98xy
字符串函数
- 标准库提供一套专门针对字符串的函数
– 字符串比较
— int strcmp(const char *s1,const char *s2);
— int strncmp(const char *s1,const char *s2,size_t n)
– 使用字符串函数时需要注意
— 所有字符串都必须以空字符结尾
— 字符串长度以char为单位且不包含结尾空字符
— 拷贝和连接的目标缓冲区必须可写且足够大
— 不能使用关系运算符比较字符串
strcmp
strcmp(字符串,字符串)
从左边到右边一个字符一个字符的比较,直到不相等或者字符串结束
“hello”
“hfllf”
字符串相等,返回0
第一个字符串大,返回1
第二个字符串大,返回-1
案例3:字符串函数
#include <stdio.h>
#include <string.h>//字符串函数头文件
extern void reverse(char *p);
int main(void){
//strcmp
int ret = 0;
ret = strcmp("abc", "abc");
printf("abc : abc = %d\n", ret);
ret = strcmp("hello", "hellf");
printf("hello : hellf = %d\n", ret);
ret = strcmp("hello", "hfllo");
printf("hello : hfllo = %d\n", ret);
ret = strcmp("r", "a");
printf("r : a = %d\n", ret);
ret = strcmp("a", "r");
printf("a : r = %d\n", ret);
printf("---------------------------------------\n");
char *p4 = "abc";
char *p5 = "bcd";
ret = strncmp(p4, p5, 2);
printf("abc : abd (2) : %d\n", ret);
return 0;
}
abc : abc = 0
hello : hellf = 1
hello : hfllo = -1
r : a = 1
a : r = -1
---------------------------------------
abc : abd (2) : -1
案例4:字符串反转
//字符串反转
#include <stdio.h>
#include <string.h>
extern void reverse(char *pstr);
int main(void){
char str[1024] = {0};
for(;;){
//gets(str);
scanf("%[^\n]", str);
reverse(str);
puts(str);
}
return 0;
}
//编辑reverse函数实现字符串反转:"123456789" -> "987654321"
void reverse(char *pstr){
int len = strlen(pstr);//获取有效字符个数
for(int i = 0; i < len/2; i++){
char c = pstr[i];
pstr[i] = pstr[len-i-1];
pstr[len-i-1] = c;
}
printf("pstr = %s\n", pstr);
}
123456
pstr = 654321
字符串数组
- 二维数组形式的字符串数组
- char sa[ ][10] = {“beijing”,“tianjin”,“shanghai”,“chongqing”};
sa | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
sa[0] | b | e | i | j | n | g | \0 | \0 | \0 | |
sa[1] | t | i | a | n | j | i | n | \0 | \0 | \0 |
sa[2] | s | h | a | n | g | h | a | i | \0 | \0 |
sa[3] | c | h | o | n | g | q | i | n | g | \0 |
- 指针数组形式的字符串数组
- const char * sa[] = {“beijing”,“tianjin”,“shanghai”,“chongqing”};
main函数参数
//例如:./hello 100 200 -> "./hello" "100" "200"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[]){
//argc:记录命令行终端输入的命令和参数的个数(操作系统自动赋值)
//./hello -> argc=1
//./hello 100 200 -> argc=3
//argv:记录命令行终端中输入的信息对应的字符串首地址
//./hello -> argv[0] -> 存储字符串./hello的首地址
//./hello 100 -> argv[0]存储./hello字符串首地址, argv[1]存储字符串100首地址
//./hello 100 200 -> argv[0]存储./hello字符串首地址, argv[1]存储字符串100首地址
//argv[2]存储字符串200首地址
for(int i = 0; i < argc; i++)
printf("argc = %d, argv[%d] = %s\n", argc, i, argv[i]);
//计算两个数字求和的结果
if(argc != 3){
printf("用法: %s 数字1 数字2\n", argv[0]);
printf("举例:%s 100 200\n", argv[0]);
return -1;
}
//atoi - ascii to int
int a, b;
a = atoi(argv[1]);
b = atoi(argv[2]);
printf("sum = %d\n", a+b);
return 0;
}
tarena@TNV:day15$ ./main 200 500 600 12 3 5
argc = 7, argv[0] = ./main
argc = 7, argv[1] = 200
argc = 7, argv[2] = 500
argc = 7, argv[3] = 600
argc = 7, argv[4] = 12
argc = 7, argv[5] = 3
argc = 7, argv[6] = 5
用法:./main 数字1 数字2
举例:./main 100 200
sum = 700
案例:字符串比较函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
extern int my_strcmp(const char *s1,const char *s2);
int main(int argc, char *argv[]){
int ret = my_strcmp("abc", "abcd");
printf("ret = %d\n", ret);
return 0;
}
int my_strcmp(const char *s1, const char *s2){
while(*s1){
if(*s1 != *s2) return *s1 - *s2;
s1++;
s2++;
}
return *s1 - *s2;
}
tarena@TNV:day15$ ./my_strcmp
ret = -100
案例:迷你备忘录
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void){
printf("===============\n");
printf(" 迷你备忘录\n");
printf("===============\n");
char event[31][256];//定义初始化二位数组保存事件信息
//初始化二位数组
for(int i = 0; i < 31; i++){
event[i][0] = '\0';//全部给0
}
for(;;){
printf("备忘信息:");
int nday = -1;
scanf("%d", &nday);//输入日期
if(nday < 0 || nday > 31){
printf("无效日期\n");
continue;
}
if(nday == 0){
break;//结束死循环,结束循环输入
}
scanf("%s", event[nday-1]);
}
printf("============================\n");
printf(" 备 忘 信 息 列 表\n");
printf("============================\n");
for(int i = 0; i < 31; i++){//打印信息列表
if(strlen(event[i])){//如果有效字符个数非0,输入备忘信息
printf("%d日:%s\n", i+1, event[i]);
}
}
printf("============================\n");
return 0;
}
tarena@TNV:day16$ gcc event.c -o event
tarena@TNV:day16$ ./event
===============
迷你备忘录
===============
备忘信息:1 study
备忘信息:2 eat
备忘信息:3 work
备忘信息:0 0
============================
备 忘 信 息 列 表
============================
1日:study
2日:eat
3日:work
============================
3.预处理
文件包含指令
- #include
-
- 将所包含文件的内容粘贴到该指令处
-
- 尖括号包含:#include <xxx.h>
– 先找-l目录,再找系统目录(/usr/include)适用于系统头文件
- 尖括号包含:#include <xxx.h>
-
- 双引号包含:#include “xxx.h”
– 先找-l目录,再找当前目录,最后找系统目录 适用于自己编写的头文件
- 双引号包含:#include “xxx.h”
-
- gcc -E -o xxx.i xxx.c
宏定义指令
- #define分类
-
- 无参宏定义(常宏量)
– #define宏名(宏值)
– 例如:#define PI (3.14)
- 无参宏定义(常宏量)
-
- 有参宏定义(宏函数)
– #define宏名(形参表)(宏值)
– 例如:#define SQUARE(X) ((X)*(X))
- 有参宏定义(宏函数)
-
- #undef
– 取消一个已定义的宏,令宏名处于未定义状态
- #undef
//编译过程:
//gcc -E define.c -o define.i
//vim define.i -> 跳转到末尾查看
#include <stdio.h>
#define PI (3.14) //定义常量宏,其值为3.14
int main(void){
double r = 10;
//垃圾代码:后期可维护性很差
printf("面积为%lg\n", 3.1415926 * r * r);
printf("周长为%lg\n", 2 * 3.14 * r);
//有效代码:可维护性强
printf("面积为%lg\n", PI *r * r);
printf("周长为%lg\n", 2 * PI * r);
return 0;
}
//gcc -E define1.c -o define1.i
//vim define1.i -> shift+G 文件末尾
#include <stdio.h>
#define SQUARE(X) (X*X) //求平方
#define SQUARE1(X) ((X)*(X)) //求平方
#define ADD(X,Y) ((X) + (Y))
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
int main(void){
printf("%d\n",SQUARE(10));//(10*10)
printf("%d\n",SQUARE(3+7));//(3+7*3+7)
printf("%d\n",SQUARE1(10));//
printf("%d\n",SQUARE1(3+7));//((3+7) * (3+7))
printf("%d\n",ADD(10,20));
printf("-------------------------------\n");
int a = 10, b = 30;
printf("%d\n",ADD(a,b));//((a)+(b))
printf("%d\n",MAX(a,b));//((a) > (b) ? (a) : (b))
#undef MAX
//printf("%d\n", MAX(a,b));//(a)>(b)?(a):(b)
return 0;
}
856
857 # 10 "define1.c"
858 int main(void){
859 printf("%d\n",(10*10));
860 printf("%d\n",(3+7*3+7));
861 printf("%d\n",((10)*(10)));
862 printf("%d\n",((3+7)*(3+7)));
863 printf("%d\n",((10) + (20)));
864
865 printf("-------------------------------\n");
866
867 int a = 10, b = 30;
868 printf("%d\n",((a) + (b)));
869 printf("%d\n",((a) > (b) ? (a) : (b)));
870
871 return 0;
872 }
tarena@TNV:day16$ gcc define1.c -o define1
tarena@TNV:day16$ ./define1
100
31
100
100
30
-------------------------------
40
30
预定义宏
两个下划线
//宏函数演示
#define MAX(x,y) ((x)>(y) ? (x) : (y))
int main(void){
//#undef MAX
//printf("%d\n", MAX(a,b));//(a)>(b)?(a):(b)
int* p = NULL;
//忘记对p赋值了
if(p == NULL){
printf("出现了空指针:");
printf("%s %s %s %s %d\n",
__DATE__, __TIME__, __FILE__, __FUNCTION__, __LINE__);
return -1;
}
return 0;
}
tarena@TNV:day16$ vim define1.c
tarena@TNV:day16$ gcc define1.c -o define1
define1.c: In function ‘main’:
define1.c:23:16: warning: implicit declaration of function ‘MAX’ [-Wimplicit-function-declaration]
printf("%d\n",MAX(a,b));//((a) > (b) ? (a) : (b))
^
/tmp/ccE1jNbi.o:在函数‘main’中:
define1.c:(.text+0xc8):对‘MAX’未定义的引用
collect2: error: ld returned 1 exit status
出现了空指针异常
Jul 18 2023 15:23:52 define1.c main 27
案例:宏的表示
//gcc -D选项使用:通过-D选项给程序传递一个常量红
//gcc -DSIZE=5 -DPRINT=\"hello\" define3.c -o define3
#include <stdio.h>
//#define SIZE (5)
int main(void){
int arr[SIZE] = {0};
//初始化数组元素
for(int i = 0; i < SIZE; i++)
arr[i] = i + 100;
//打印数组元素
for(int i = 0; i < SIZE; i++)
printf("%d ", arr[i]);
printf("\n");
printf("%s\n", PRINT);
return 0;
}
tarena@TNV:day16$ gcc -DSIZE=5 -DPRINT=\"hello\" define3.c -o define3
tarena@TNV:day16$ ./define3
arr[0] = 100
arr[1] = 101
arr[2] = 102
arr[3] = 103
arr[4] = 104
hello
条件编译指令 - 满足条件编译,不满足编译器
#define D (100)
#ifndef D -> if not define ->
-> 如果没有定义D这个宏, 条件成立, 为真
-> 反之,如果定义D宏, 条件不成立, 为假
#ifdef D -> if define
-> 如果定义D宏, 条件成立, 为真
-> 如果没有定义D这个宏, 条件不成立, 为假
#elif -> else if
#include <stdio.h>
#define A (11)
#define B (1)
#define E (100)
int main(void){
#if A == 1
printf("1\n");
#endif
#if B == 1
printf("3\n");
#else
printf("4\n");
#endif
#ifndef C
printf("c\n");
#else
printf("not def c\n");
#endif
#ifdef D
printf("6\n");
#elif E == 10
printf("7\n");
#else
printf("8\n");
#endif
return 0;
}
tarena@TNV:day16$ vim if.c
tarena@TNV:day16$ gcc if.c -o if
tarena@TNV:day16$ ./if
3
c
8
让一套代码适应于不同平台
例如Linux内核, 运行 x86 arm PowerPC mips ...
cpu_init(void){
//和平台无关代码
...(90%)
#if ARCH==x86
x86 cpu的初始化代码
#elif ARCH==arm
arm cpu的初始化代码
#elif ARCH=mips
mips cpu的初始化代码
....
#endif
}
要想生成适应于arm平台的代码
gcc -DARCH=arm xxx.c yyy.x ... -o ...
#ifdef D == #if defined(D)
4.头文件编程
头文件与头文件卫士
多文件编程
mkdir include
main.c
a.h
b.h
============================================
//main.c
#include <stdio.h>
//#include "a.h"
int a = 520;//定义一次
//#include "b.h"
//#include "a.h"
int a = 520;//定义2次 - 重定义
int main(void){
printf("a = %d\n", a);
return 0;
}
tarena@TNV:day17$ gcc main.c -o main
tarena@TNV:day17$ ./main
a = 520
大型程序文件分类
- 头文件:
– cal.h - 源文件:
– cal.c - 主文件
– main.c
头文件与头文件卫士
-
头文件中放什么?
– 包含公共头文件 #include <stdio.h>
– 宏定义 #define TRUE 1
– 类型定义 typedef int BOOL
– 函数声明 int ConnectServer (const char *URL);
– 外部变量声明 extern const char *g_dns;
– 例如:cal.h -
源文件放什么
– 全局变量的定义
– 函数的定义
– 例如:main.c
– 源文件包含自己的头文件
– main.c
– #include “cal.h” -
主文件(main函数所在的文件,例如: main.c)放什么?
– main 函数和调用
tarena@TNV:exec$ gcc main.c cal.c -o main
tarena@TNV:exec$ ./main
110
80
5.Makefile
- make是一种
文件转换工具
,通过某种操作,将依赖文件转换为目标文件 - make命令可以根据makefile脚本中定义的
规则
,完成从依赖
文件到目标
文件的转换 - 只有当依赖文件比目标文件
新
时,make才会重新生成目标文件 - 具体到C语言程序,可以认为
可执行程序
依赖于目标模块
,而目标模块又依赖于源程序和头文件
- 从源程序和头文件到目标模块,以及从目标模块到可执行程序所需要的操作就是各种
GCC
命令 - makefile需要描述的就是
目标、依赖
以及从依赖产生目标所需要执行的各种命令
- makefile的基本语法要素
目标:依赖
<制表符>命令1
<制表符>命令2
- 一个makefile可以包含多个目标,通过make命令的参数指定期望实现的目标,
缺省
实现第一个
- 命令可以是GCC命令,也可以是普通
SHELL
命令 - 目标的依赖和/或命令可以为空
Makefile
就是一个文本文件 - 特殊 - 存储的是文件的编译的规则
将来可以让程序按照文件中的编译规则进行编译
Makefile是给make命令来使用的
执行make后,根据Makefile中所指定的规则进行编译
目标:依赖
(tab)命令
目标文件 - 想要得到的文件
依赖文件 - 目标文件所依赖的文件
命令 - 如何由依赖文件得到目标文件
gcc hello.c -o hello
vim Makefile
hello:hello.c
(tab)gcc hello.c -o hello
案例
西红柿,鸡蛋,调料 - 依赖文件
菜谱 - Makefile
厨师 - make
西红柿炒鸡蛋 - 目标
gcc -c main.c -o main.o
gcc -c arr.c -o arr.o
gcc -c cal.c -o cal.o
gcc main.o arr.o cal.o -o main
vim Makefile
编译规则
tarena@TNV:exec$ cat Makefile
#编译规则1
main:main.o cal.o arr.o swap.o sum.o
gcc main.o cal.o arr.o swap.o sum.o -o main
#编译规则2
main.o:main.c
gcc -c main.c -o main.o
#编译规则3
cal.o:cal.c
gcc -c cal.c -o cal.o
#编译规则4
arr.o:arr.c
gcc -c arr.c -o arr.o
#编译规则5
swap.o:swap.c
gcc -c swap.c -o swap.o
#编译规则6
sum.o:sum.c
gcc -c sum.c -o sum.o
tarena@TNV:exec$ ls
arr.c arr.h arr.o cal.c cal.h cal.o main main.c main.o Makefile sum.c sum.h sum.o swap.c swap.h swap.o
tarena@TNV:exec$ rm *.o main
tarena@TNV:exec$ ls
arr.c arr.h cal.c cal.h main.c Makefile sum.c sum.h swap.c swap.h
tarena@TNV:exec$ make
gcc -c main.c -o main.o
gcc -c cal.c -o cal.o
gcc -c arr.c -o arr.o
gcc -c swap.c -o swap.o
gcc -c sum.c -o sum.o
gcc main.o cal.o arr.o swap.o sum.o -o main
tarena@TNV:exec$ ./main
110
80
-----------------
9
1 2 3 4 5 6 7 8 9
----------------
p = 10, q = 20
p = 20, q = 10
-------求和------
ret = 5050
Makefile小技巧:
vim Makefile
依赖 - 直接依赖
检查:
1.最终目标是否存在 - 不存在 - 直接编译
2.最终目标存在
检查 - 最终目标是不是最新的
- 源文件的时间戳 - 是否比生成可执行程序的事件更新
比最终目标更新 - 重新编译
最终目标更新 - 不再重新编译
Makefile小技巧:
1.变量
类似于C语言中的宏
OBJ=main.o
使用:
$(OBJ) 等价于 main.o
2.让.c文件生成对应的.o文件
%.o:%.c
gcc -c $< -o $@
%.o, $@ - 目标文件 - .o文件
%.c, $< - 依赖文件 - .c文件
3.伪目标
目标 - 没有依赖 -> 伪目标
clean:
命令
make clean - clean对应的命令
伪目标
tarena@TNV:exec$ cat Makefile
#类似于C语言里面的宏
OBJ=main.o cal.o arr.o swap.o sum.o
BIN=main
CC=gcc
RM=rm
#编译规则1
$(BIN):$(OBJ)
$(CC) $(OBJ) -o $(BIN)
#编译规则2:让.c文件生成对应的.o文件
%.o:%.c
$(CC) -c $< -o $@
#伪目标
clean:
$(RM) $(OBJ) $(BIN)
6.结构体
- 结构是一种由
若干成员
组成的构造类型 - 结构的成员既可以是
基本类型
的数据,也可以又是结构类型
的数据 - 结构是─种构造而成的数据类型,在使用结构之前必须
先行定义
,因此结构也是一种自定义
数据类型 - 结构类型的数据在存储上类似于数组,所有成员按其被声明的顺序,由
低
地址到高
地址连续
(或接近连续)存放 - 与数组不同,结构的成员可以是不同类型的数据,因此结构也是一种
复合
数据类型
结构体是分配内存的第三种方法
变量
数组
int a;//4字节
int arr[100];//分配大量内存 数据类型保持一致
有些场合需要分配不同种类的数据类型
分配一段内存去描述一个人的信息
信息 数据类型
姓名 char name[32]
年龄 unsigned char
id unsigned int
工资 double
int a[5] = {1,2,3,4,5};
a[0] a[1] a[2] a[3] a[4] - 称之为数组a的5个成员
5个成员 - 类型相同
结构体 - 成员 - 数据类型可以不同
结构体是用户自己定义的数据类型 - 定义变量
结构体变量玩法1
//定义结构体变量玩法1:直接定义结构体变量
#include <stdio.h>
#include <string.h>
int main(void){
//直接定义结构体数据类型的变量s1并且初始化
struct{
char name[32];//存储名字
int age;//存储年龄
}s1 = {"james", 33};
//打印学生信息
//通过变量名访问其中的成员语法:变量名.成员名
printf("%s, %d\n", s1.name, s1.age);
s1.age = 39;
strcpy(s1.name, "James");
printf("%s, %d\n", s1.name, s1.age);
return 0;
}
tarena@TNV:exec$ vim struct.c
tarena@TNV:exec$ gcc struct.c -o struct
tarena@TNV:exec$ ./struct
james, 33
James, 39
结构体变量玩法2
//结构体变量玩法2
//先声明数据类型,然后再定义变量
#include <stdio.h>
#include <string.h>
//声明结构体数据类型
struct student{
char name[32];
int age;
};
int main(void){
//定义结构体类型变量
struct student s1 = {"james", 520};
//定义结构体指针变量
struct student* ps1 = &s1;
//通过指针变量访问结构体成员语法:指针变量->成员名
printf("%s, %d\n", ps1->name, ps1->age);
ps1->age = 134;
strcpy(ps1->name, "wukong");
printf("%s, %d\n", ps1->name, ps1->age);
printf("%s, %d\n", s1.name, s1.age);
return 0;
}
tarena@TNV:exec$ vim struct2.c
tarena@TNV:exec$ gcc struct2.c -o struct2
tarena@TNV:exec$ ./struct2
james, 520
wukong520, 134
wukong520, 134
结构体变量玩法3–声明描述学生信息的结构体类型
- 指向结构变量和结构数组的指针
– 结构指针的计算以整个结构的大小为单位 - 访问结构的成员
– 通过结构变量访问其成员,用成员访问运算符“."
– 通过结构指针访问其目标的成员,用间接成员访问运算符“->" - 如果函数的参数是结构类型,那么形参只是实参的拷贝,在函数内部无法修改实参的成员
- 如果函数的参数是结构指针,并接受实参的地址,那么就可以在函数内部修改实参的成员
//结构体数组
#include <stdio.h>
//声明描述学生信息的结构体类型
typedef struct student{
char name[32];
int age;
}stu_t;
int main(void){
//定义结构体数组
stu_t stu_info[3] = {
{"zhangsan", 19},
{"liszi", 18},
{"wangwu", 23}
};
int size = sizeof(stu_info) / sizeof(stu_info[0]);
for(int i = 0; i < size; i++){
printf("%s, %d \n",stu_info[i].name, stu_info[i].age);
}
for(int i = 0; i < size; i++){
stu_info[i].age += 20;
}
for(int i = 0; i < size; i++){
printf("%s, %d \n",stu_info[i].name, stu_info[i].age);
}
return 0;
}
tarena@TNV:day18$ vim struct.c
tarena@TNV:day18$ gcc struct.c -o struct
tarena@TNV:day18$ ./struct
zhangsan, 19
liszi, 18
wangwu, 23
zhangsan, 39
liszi, 38
wangwu, 43
结构体和函数的关系
结构体 + 数组 = 结构体数组
本质上就是一个数组, 只是该数组每个元素都是结构体类型
stu_t s[50];
int a[3] = {1,2,3};
结构体和函数的关系:
结构体作为函数的参数
建议使用指针 - 效率更高
A函数 -> 结构体变量 -> B函数
A函数 -> 结构体变量指针 -> B函数
4 / 8
结构体作为函数的返回值
结构体变量 - 结构体指针
局部变量 - 函数结束 -
内存释放 - 不能返回局部变量的地址
结构体
#include <stdio.h>
typedef struct student{
char name[32];
int age;
}stu_t;
void show(stu_t st){//st = s1
printf("%s, %d\n",st.name, st.age);
}
//常量指针不可以进行修改,只能读取
void show_p(const stu_t *pst){
//pst -> age++;
printf("%s, %d\n",pst -> name, pst -> age);
}
void grow(stu_t st){//st = s1
st.age++;//修改的是形参st的值,和变量s1没有关系
}
void grow_p(stu_t *pst){//pst = &s1
pst -> age++;//直接修改了实参s1的age的值
}
stu_t get_stu_info(void){
stu_t s = {"liubei", 25};
return s;
}
//局部变量 - 函数结束 - 内存释放 - 不能返回局部变量的地址
//error
/*
stu_t *get_stu_info(void){
stu_t s = {"zhugeliang", 34};//结构体类型的变量,局部变量
return &s;//不可以返回局部变量的地址
}
*/
int main(void){
stu_t s1 = {"xiaobai", 19};
show(s1);
printf("---------------------------\n");
grow(s1);
show(s1);
printf("---------------------------\n");
grow_p(&s1);
show(s1);
printf("---------------------------\n");
show_p(&s1);//s1的age是否会加1呢
show(s1);
printf("---------------------------\n");
stu_t stu = get_stu_info();//使用stu保存该函数的返回值
printf("%s, %d\n",stu.name, stu.age);
printf("---------------------------\n");
return 0;
}
tarena@TNV:day18$ vim struct2.c
tarena@TNV:day18$ gcc struct2.c -o struct2
tarena@TNV:day18$ ./struct2
xiaobai, 19
---------------------------
xiaobai, 19
---------------------------
xiaobai, 20
---------------------------
xiaobai, 20
xiaobai, 20
---------------------------
liubei, 25
---------------------------
嵌套结构体
结构体包含不同类型的成员
1.结构体类型的成员
2.结构体指针类型的成员
birth_t* p; 就是指针变量 -> 初始化 -> NULL
直到让p指向谁吗? - 不知道 - NULL
结构体变量 - 初始化
结构体数组 - 初始化
结构体成员 - 初始化
- 完全一样
结构体变量 / 指针变量 -
只能决定如何访问当前结构体的成员
结构体内存对齐和补齐 -
默认 4字节对齐补齐
1字节对齐补齐
2字节对齐补齐
4字节对齐补齐
8字节对齐补齐
#pragma pack(n)
....
....
#pragma pack(pop)
结构体成员为结构体类型变量
//结构体成员为结构体类型变量
//描述学生出生日期的结构体
#include <stdio.h>
typedef struct birthday{
int year;
int month;
int day;
}birth_t;
//描述学生信息的结构体
typedef struct student{
char name[32];
int age;
birth_t birth;//结构体类型的成员
}stu_t;
int main(void){
//定义结构体类型变量
stu_t s1 = {"James", 29, {2000, 1, 1}};
stu_t *ps1 = &s1;
//ps1->name ps1->age ps1->birth
//成员的访问
//s1
//s1.name s1.age s1.birth
//birth_t birth
//birth.year birth.month birth.day
printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
s1.name, s1.age, s1.birth.year, s1.birth.month, s1.birth.day);
printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
ps1->name, ps1->age, ps1->birth.year,ps1->birth.month,ps1->birth.day);
s1.age = 43;
s1.birth.year = 2013;
printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
s1.name, s1.age, s1.birth.year, s1.birth.month, s1.birth.day);
printf("姓名:%s,年龄:%d岁,出生日期:%d年%d月%d日\n",
ps1->name, ps1->age, ps1->birth.year,ps1->birth.month,ps1->birth.day);
return 0;
}
tarena@TNV:day18$ vim struct4.c
tarena@TNV:day18$ gcc struct4.c -o struct4
tarena@TNV:day18$ ./struct4
姓名:James,年龄:29岁,出生日期:2000年1月1日
姓名:James,年龄:29岁,出生日期:2000年1月1日
姓名:James,年龄:43岁,出生日期:2013年1月1日
姓名:James,年龄:43岁,出生日期:2013年1月1日
结构体成员为结构体指针类型变量
//结构体成员为结构体指针类型变量
//描述学生出生日期的结构体
#include <stdio.h>
#include <string.h>
typedef struct birthday{
int year;
int month;
int day;
}birth_t;
//描述学生信息的结构体
typedef struct student{
char name[32];
int age;
birth_t *pbirth;//结构体指针类型的成员
//就是指针变量
}stu_t;
int main(void){
//定义结构体类型的变量
stu_t s1 = {"james", 29, NULL};
//定义birth_t类型变量
birth_t birth = {1993, 9, 28};
//给james同学关联对应的出生日期,此时pbirth就会指向一个有效内存
s1.pbirth = &birth;
//birth_t *pbirth;
//pbirth->year ,pbirth->month, pbirth->day
printf("%s, %d, (%d, %d, %d) \n",s1.name, s1.age, s1.pbirth->year, s1.pbirth->month, s1.pbirth->day);
strcpy(s1.name, "James");
s1.age = 52;
s1.pbirth->year = 1952;
printf("%s, %d, (%d, %d, %d) \n",s1.name, s1.age, s1.pbirth->year, s1.pbirth->month, s1.pbirth->day);
return 0;
}
tarena@TNV:day18$ vi struct5.c
tarena@TNV:day18$ gcc struct5.c -o struct5
tarena@TNV:day18$ ./struct5
james, 29, (1993, 9, 28)
James, 52, (1952, 9, 28)
两个圆
#include <stdio.h>
#include <math.h>
#define PI (3.14) //定义常量宏,其值为3.14
//声明描述一个点的位置结构体
typedef struct{
int row;
int col;
}pt;
//声明描述一个圆的结构体
typedef struct{
pt c;//圆心
int radius;//半径
}cir_t;
cir_t *bigger(const cir_t *p1, const cir_t *p2){
return (cir_t *)(p1->radius >= p2->radius ? p1 : p2);
}
//计算圆的面积
double area(const cir_t *p){
return PI * p->radius * p->radius;
}
//计算圆的周长
double cir(const cir_t *p){
return 2 * PI * p->radius;
}
//计算两个圆心的距离平方
double dis(const cir_t *p1, const cir_t *p2){
return ( (p1->c.row - p2->c.row)*(p1->c.row - p2->c.row)+(p1->c.col - p2->c.col)*(p1->c.col - p2->c.col) );
}
//计算长方形的面积
double area_c(const cir_t *p1, const cir_t *p2){
return (p1->c.row - p2->c.row) * (p1->c.col - p2->c.col);
}
int main(void){
cir_t c1 = {0}, c2 = {0}, *p = NULL;
printf("请输入一个圆:");
scanf("%d%d%d", &(c1.c.row), &(c1.c.col), &(c1.radius));
printf("请输入一个圆:");
scanf("%d%d%d", &(c2.c.row), &(c2.c.col), &(c2.radius));
p = bigger(&c1, &c2);
printf("更大的圆是(%d, %d), %d\n",p->c.row, p->c.col, p->radius);
printf("圆的面积为:%lg\n", area(&c1));
printf("圆的面积为:%lg\n", area(&c2));
printf("圆的周长为:%lg\n", cir(&c1));
printf("圆的周长为:%lg\n", cir(&c2));
printf("两个圆心之间的距离平方:%lg\n", dis(&c1, &c2));
printf("长方形的面积:%lg\n", area_c(&c1, &c2));
return 0;
}
tarena@TNV:day18$ vim struct6.c
tarena@TNV:day18$ gcc struct6.c -o struct6
tarena@TNV:day18$ ./struct6
请输入一个圆:3 4 5
请输入一个圆:6 8 10
更大的圆是(6, 8), 10
圆的面积为:78.5
圆的面积为:314
圆的周长为:31.4
圆的周长为:62.8
两个圆心之间的距离平方:25
长方形的面积:12
7.联合 - UNION
一.联合体
联合体 - 判断主机字节序 - 笔试题
主机字节序 - 电脑存储方式
大端存储:将数据的低位保存在内存的高地址处,而数据的高位,保存在内存的低地址中。
小端存储:将数据的低位保存在内存的低地址处,而数据的高位,保存在内存的高地址中。
int a = 0x12345678;
判断当前主机字节序 -
大端
小端
x86 arm - 大端,小端 - 小端
PowerPC, 网络 - 大端
大端存储:将数据的低位保存在内存的高地址处,而数据的高位,保存在内存的低地址中。
小端存储:将数据的低位保存在内存的低地址处,而数据的高位,保存在内存的高地址中。
int num = 0x11223344;
低地址 -> 高地址
大端存储:11 22 33 44
小端存储:44 33 22 11
#include <stdio.h>
int main(void){
union{
unsigned int u_n;
unsigned char u_c[4];
}mb;//直接定义联合体变量mb,分配4个字节
mb.u_n = 0x12345678;//向4个字节内存中写入了数据0x12345678
printf("%#x\n", mb.u_n);//0x12345678
//0x78 0x56 0x34 0x12 u_n
//__ __ __ __
//u_c[0] u_c[1] u_c[2] u_c[3] u_c
for(int i = 0; i < 4; i++){
printf("%#x ", mb.u_c[i]);
}
printf("\n");
//0x78 0x56 0x43 0x12 u_n
//__ __ __ __
//u_c[0] u_c[1] u_c[2] u_c[3] u_c
mb.u_c[2] = 0x43;//0x34 -> 0x43
printf("%#x\n", mb.u_n);//0x12435678
//使用联合求主机字节序
mb.u_n = 1;//0x00 00 00 01
// __ __ __ __
//little 01 00 00 00
//big 00 00 00 01
if(mb.u_c[0] == 1){
printf("little\n");
}else{
printf("big\n");
}
//指针 - 强转获取第一个字节的数据 - 1,little;0;big
return 0;
}
tarena@TNV:day19$ vim union.c
tarena@TNV:day19$ gcc union.c -o union
tarena@TNV:day19$ ./union
0x12345678
0x78 0x56 0x34 0x12
0x12435678
little
经典使用场景
struct student:
姓名 性别 班级
struct teacher:
姓名 性别 职称
结构体 套 联合
联合 - 对于同一块内存的不同使用方式
#include <stdio.h>
//声明结构体数据类型
typedef struct person{
char name[32];
char sex;//'m' 'f'
char job;//'s' 't'
union{
int class;//班级
char position[10];//位置/职称 讲师 教授
}category;
}per_t;
int main(void){
per_t p[2] = {
{.name = "小明", .sex = 'm', .job = 's', .category.class = 2208},
{.name = "老张", .sex = 'f', .job = 't', .category.position = "教授"}
};
for(int i = 0; i < 2; i++){
if(p[i].job == 's'){
printf("姓名:%s, 性别:%c, 班级:%d \n",
p[i].name, p[i].sex, p[i].category.class);
}else{
printf("姓名:%s, 性别:%c, 职称:%s \n",
p[i].name, p[i].sex, p[i].category.position);
}
}
return 0;
}
tarena@TNV:day19$ vim union2.c
tarena@TNV:day19$ gcc union2.c -o union2
tarena@TNV:day19$ ./union2
姓名:小明, 性别:m, 班级:2208
姓名:老张, 性别:f, 职称:教授
8.枚举 - enum
宏 - 没有数据类型 - 文本替换
枚举 - 有数据类型 - int类型
提高代码可读性
#include <stdio.h>
enum COLOR{
GREEN,
BLUE,
RED,
YELLOW
};
int main(void){
printf("%d %d %d %d \n", GREEN, BLUE, RED, YELLOW);
return 0;
}
tarena@TNV:day19$ vim enum.c
tarena@TNV:day19$ gcc enum.c -o enum
tarena@TNV:day19$ ./enum
0 1 2 3
#include <stdio.h>
enum RETURN{
OK,
FAILED
};//0 1
typedef enum RETURN return_t;
return_t check_enum(int a){
if(a != 0){
printf("success\n");
return OK;
}else{
printf("failed\n");
return FAILED;
}
}
int check(int a){
if(a != 0){
printf("success\n");
return 0;
}else{
printf("failed\n");
return 1;
}
}
int main(void){
printf("%d\n", check(0));
printf("%d\n", check(100));
printf("--------------------\n");
printf("%d\n", check_enum(0));
printf("%d\n", check_enum(100));
return 0;
}
tarena@TNV:day19$ vim enum2.c
tarena@TNV:day19$ gcc enum2.c -o enum2
tarena@TNV:day19$ ./enum2
failed
1
success
0
--------------------
failed
1
success
0
9.高级指针(非常核心)
指针 - 变量, 数组, 结构体
只要想要指向的内容在内存中 - 合法 - 都可以使用指针指向该内容
如何定义指针变量 - 让该指针变量指向该函数 - 使用该函数
函数名 - 函数首地址 - %p - 如何验证
让pfunc指向了add - 成立
pfunc == add
如果使用函数 - 如何使用函数指针变量 -
函数指针变量 - 变量
普遍性 - 变量 - 指向其他地址
特殊性 - 函数指针变量
定义另一个函数指针变量 - 指向sub函数
int (*pfunc2)(int, int) = sub;//ok - 麻烦
typedef - 起别名
普遍性 - 特殊性
int* - 指向了int - int
int* p;
char* - 指向了char - char
char* p;
int (*pfunc)(int, int) - int ()(int, int)
函数的参数 - 函数指针变量作为形参
变量 数组 结构体
函数 - 回调函数 - Qt - 信号和槽 - 回调函数
#include <stdio.h>
int add(int x, int y){
return x + y;
}
int sub(int x, int y){
return x - y;
}
int main(void){
printf("main:%p\n", main);
printf("add:%p\n", add);
printf("sub:%p\n", sub);
int ret = 0;
//如何定义一个指针变量存储函数的首地址
//函数指针变量 - 指向哪个函数 - add - 函数指针变量 + 参数 + 返回值
//初始化 - 函数名
//定义函数指针变量pfunc保存了add函数的首地址
int (* pfunc)(int, int) = add;
//ret = add(100, 200);
//如何使用函数,就如何使用指针变量
ret = pfunc(100, 200);
printf("add函数的返回值为:%d\n", ret);//300
printf("pfunc:%p\n", pfunc);
//让pfunc指向sub函数 - ok
//pfunc存储了sub函数的首地址
pfunc = sub;
printf("pfunc:%p\n", pfunc);
ret = pfunc(300, 10);// == sub(300,10); == 290
printf("sub函数的返回值为:%d\n", ret);//290
printf("pfunc:%p\n", pfunc);
return 0;
}
tarena@TNV:day19$ vim pfunc.c
tarena@TNV:day19$ gcc pfunc.c -o pfunc
tarena@TNV:day19$ ./pfunc
main:0x40054c
add:0x400526
sub:0x40053a
add函数的返回值为:300
pfunc:0x400526
pfunc:0x40053a
sub函数的返回值为:290
pfunc:0x40053a
#include <stdio.h>
int add(int x, int y){
return x + y;
}
int sub(int x, int y){
return x - y;
}
//什么数据类型
//int (*pfunc)(int, int) - 什么数据类型 - 将变量名去掉 - 变量的数据类型
//int (*)(int, int) - 函数指针变量的数据类型 - 不存在 - 自定义数据类型 - 取别名
//typedef int (*pfunc_t)(int, int); - 后续可以直接以pfunc_t定义函数指针变量
//注意:将新的数据类型名字写在*的后面
typedef int (* pfunc_t)(int, int);
int main(void){
printf("main:%p\n", main);
printf("add :%p\n", add);
printf("sub :%p\n", sub);
int ret = 0;
//定义函数指针变量pfunc指向add函数
pfunc_t pfunc = add;
ret = pfunc(100, 200);
printf("add函数返回值:%d\n", ret);//300
//定义函数指针变量pfunc2指向sub函数
pfunc_t pfunc2 = sub;
ret = pfunc2(100, 200);
printf("sub函数返回值:%d\n", ret);//-100
return 0;
}
tarena@TNV:day19$ vim pfunc2.c
tarena@TNV:day19$ gcc pfunc2.c -o pfunc2
tarena@TNV:day19$ ./pfunc2
main:0x40054c
add :0x400526
sub :0x40053a
add函数返回值:300
sub函数返回值:-100
使用typedef给定义函数指针变量数据类型
#include <stdio.h>
//使用typedef给定义函数指针变量数据类型
typedef int(* pfunc_t)(int, int);
int add(int x, int y){
printf("enter %s \n", __func__);
return x + y;
}
int sub(int x, int y){
printf("enter %s \n", __func__);
return x - y;
}
int mul(int x, int y){
printf("enter %s \n", __func__);
return x * y;
}
int div(int x, int y){
printf("enter %s \n", __func__);
return x / y;
}
int mod(int x, int y){
printf("enter %s \n", __func__);
return x % y;
}
//定义函数指针数组,数组中的每个元素都是一个函数的地址
pfunc_t a[] = {add, sub, mul, div, mod};
int main(void){
int len = sizeof(a) / sizeof(a[0]);
pfunc_t pfunc = NULL;
int ret = 0;
for(int i = 0; i < len; i++){
pfunc = a[i];
ret = pfunc(200, 20);
printf("ret = %d\n", ret);
}
return 0;
}
tarena@TNV:day19$ vim pfunc3.c
tarena@TNV:day19$ gcc pfunc3.c -o pfunc3
tarena@TNV:day19$ ./pfunc3
enter add
ret = 220
enter sub
ret = 180
enter mul
ret = 4000
enter div
ret = 10
enter mod
ret = 0
回调函数 - 信号和槽 - QT - 引以为傲
//回调函数 - 信号和槽 - QT - 引以为傲
#include <stdio.h>
//声明函数指针类型并且取别名
typedef int (* pfunc_t)(int, int);
//定义回调函数add和sub,意思就是可以将这些函数做为函数的参数传递给其他函数
int add(int x, int y){
return x + y;
}
int sub(int x, int y){
return x - y;
}
//定义cal函数,想要使用cal函数来调用add函数和sub函数
//将来需要通过函数指针变量来间接调用回调函数
//如果在某个调用函数中,将形参设置为另一个函数的首地址,间接调用对应的函数
//第三个形参pfunc为函数指针类型,函数为首地址
//cal(100, 200, add) -> pfunc = add
int cal(int a, int b, pfunc_t pfunc){
if(pfunc == NULL){
return a * b;//如果没有给cal函数传递回调函数,设置一个默认功能,计算乘法功能
}
return pfunc(a, b);
}
int main(void){
//第三个参数传递的是add,为add函数的首地址,此时的cal函数可以间接的调用add函数
printf("%d\n", cal(100, 200, add));
printf("%d\n", cal(100, 200, sub));
printf("%d\n", cal(100, 200, NULL));//使用cal函数默认的功能,计算乘法的结果
return 0;
}
tarena@TNV:day20$ vim pfunc.c
tarena@TNV:day20$ gcc pfunc.c -o pfunc
tarena@TNV:day20$ ./pfunc
300
-100
20000
指针数组和数组指针
//数组指针演示
#include <stdio.h>
int main(void){
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
//printf("a[%d][%d] = %d\n", i, j, a[i][j]);
printf("%d ", a[i][j]);
}
printf("\n");
}
printf("\n");
//定义数组指针指向二维数组
int (* p)[4] = a;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
//printf("a[%d][%d] = %d\n", i, j, p[i][j]);
printf("%d ", p[i][j]);
}
printf("\n");
}
printf("\n");
//二维数组转换为一维数组
//a -> int * ->数组中每一个元素都是int类型
int *p2 = (int *)a;//强制将数组指针类型a转换为普通的int 类型指针
for(int i = 0; i < 12; i++){
printf("%d ", p2[i]);
}
printf("\n");
printf("\n");
//一维数组转换为二维数组
//一维数组 - 二维数组
//设置 - 数组arr中的每一个元素都是4个int类型的数组
int arr[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (* p3)[4] = (int (*)[4])arr;//arr - 数组指针 - 数组中元素位4个int类型的数组
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
printf("%d ", p3[i][j]);
}
printf("\n");
}
return 0;
}
tarena@TNV:day20$ vim pointer.c
tarena@TNV:day20$ gcc pointer.c -o pointer
tarena@TNV:day20$ ./pointer
1 2 3 4
5 6 7 8
9 10 11 12
1 2 3 4
5 6 7 8
9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4
5 6 7 8
9 10 11 12
二级指针与多级指针
- 指针变量的地址即指针的指针
- 将一级指针变量的地址保存在另一个指针变量中即构成二级指针
- 二级指针是指向一级指针的指针
- 常用二级指针类型的函数形参,接受一级指针地址形式的实参,以修改调用者指针的目标,或为其分配资源
- 对一维数组的数组名取地址,得到的不是二级指针而是数组指针
#include <stdio.h>
int main(void){
int a = 100;
int *p = &a;//p为一级指针
int **pn = &p;//pn为二级指针 - 指针的指针,pn存储的是变量p的首地址
printf("a = %d\t, &a = %p\t\n", a, &a);
printf("p = %p\t, &p = %p\t, *p = %d\t\n", p, &p, *p);
//重点
printf("pn = %p\t, &pn = %p\t, *pn = %p\t, **pn = %d\t\n", pn, &pn, *pn, **pn);
printf("**pn = %d\n", **pn);
**pn = 200;//修改a的值
printf("**pn = %d\n", **pn);
return 0;
}
tarena@TNV:day20$ vim ppointer.c
tarena@TNV:day20$ gcc ppointer.c -o ppointer
tarena@TNV:day20$ ./ppointer
a = 100 , &a = 0x7ffde0aac814
p = 0x7ffde0aac814 , &p = 0x7ffde0aac818 , *p = 100
pn = 0x7ffde0aac818 , &pn = 0x7ffde0aac820 , *pn = 0x7ffde0aac814 , **pn = 100
**pn = 100
**pn = 200
一级指针内容交换–swap函数
实现字符串的交换
char* pa = “hello”;
char* pb = “world”;
定义swap函数实现pa和pb指向的交换.
pa指向"hello"
pb指向"world
#include <stdio.h>
//如果一个函数修改一个指针的内容,必须传递二级指针才可以
//传递二级指针
extern void swap(char **ps1, char **ps2){ // ps1 = &pa, ps2 = &pb
//ps1 = &pa; *ps1 = *(&pa) = pa;
//ps2 = &pb; *ps2 = *(&pb) = pb;
//*ps1 == pa - char *
char *temp = *ps1;//temp = pa
*ps1 = *ps2;//pa = pb
*ps2 = temp;//pb = temp = pa
}
int main(void){
char *pa = "hello";
char *pb = "world";
printf("pa = %s,&pa = %p\n",pa, &pa);
printf("pb = %s,&pb = %p\n",pb, &pb);
swap(&pa, &pb);
printf("\n");
/*
char *tmp = pa;
pa = pb;
pb = tmp;
*/
printf("pa = %s,&pa = %p\n",pa, &pa);
printf("pb = %s,&pb = %p\n",pb, &pb);
return 0;
}
tarena@TNV:day20$ vim swap.c
tarena@TNV:day20$ gcc swap.c -o swap
tarena@TNV:day20$ ./swap
pa = hello,&pa = 0x7ffda6c6ce68
pb = world,&pb = 0x7ffda6c6ce70
pa = world,&pa = 0x7ffda6c6ce68
pb = hello,&pb = 0x7ffda6c6ce70
二级指针–字符指针数组
如果在一个函数中修改一个指针的内容, 必须传递二级指针才可以
二级指针 - 字符指针数组
char *arr[] = {"hello", "world"};
//arr[0] - 字符串hello的首地址
//arr[1] - 字符串world的首地址
数组arr中的每个元素 - 什么类型 - char*
等价于
char *p1 = "hello";
char *p2 = "world";
char *arr[] = {p1, p2};//arr[0] = p1, arr[1] = p2;
数组中每个元素都是char*类型的指针(地址)
arr - 数组名 - 数组的首地址 - 存储的元素 - 地址
arr是一个地址,地址上存储的还是一个地址 - arr具有二级指针的意思
可以定义一个二级字符指针变量保存字符指针数组的首地址
char **parr = arr;//arr[0]指针变量 arr是arr[0]的首地址
数组名 - 地址
使用parr和arr访问数组元素的时候 - 通用性
差异:
arr++;//error
sizeof(parr) = 4/8
char a; - 字符
char *a; - 字符
int main(int argc, char* argv[]){
...
}
int main(int argc, char** argv[]){
}
char arr[] = {3,4,5};
char *p = arr;//
#include <stdio.h>
int main(void){
char *arr[] = {"hello", "world"};
printf("%s %s\n",arr[0], arr[1]);
char **p = arr;//定义二级指针p指向arr数组
printf("%s %s\n",p[0], p[1]);
char *p1 = "hi";
char *p2 = "jim";
char *arr1[] = {p1, p2};//arr1[0] = p1, arr[1] = p2
printf("%s %s\n",arr1[0], arr1[1]);
return 0;
}
tarena@TNV:day20$ vim ppstring.c
tarena@TNV:day20$ gcc ppstring.c -o ppstring
tarena@TNV:day20$ ./ppstring
hello world
hello world
hi jim
10.动态分配内存
分配内存的第四种方法
变量
数组
结构体
int a;
int a[5];
结构体变量;
都属于静态分配内存的方式 - 占用的内存空间大小编辑代码的时候就已经定死了
如果做到动态的内存空间分配
#include <stdlib.h>
动态分配内存与释放 – malloc - memory allocation - 内存分配
- 分配内存
– #include <stdlib.h>
– void *malloc(size_t size);
– 从堆中分配size字节内存
– 成功返回该内存的起始地址,失败返回NULL
– 对所分配的内存不做初始化
– 若size取0,则返回NULL或者一个唯一的地址,保证后续对free函数的调用能够成功
malloc - memory allocation - 内存分配
void* malloc (size_t size);
分配size个字节的内存空间 - 堆区 - 特点
直到使用free函数将其内存空间释放
直到程序结束
- 记得释放内存空间 - free
返回值:void* 返回分配内存的首地址
如果分配失败 - 返回NULL
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *p = NULL;
//在堆区中分配8个字节
p = (int *)malloc(8);
if(NULL == p){
printf("memory allocation failed \n");
return -1;
}
printf("分配内存成功,首地址为:%p\n", p);
//对内存进行处理
//...
*(p + 0) = 100;
*(p + 1) = 200;
printf("%d, %d\n", p[0], p[1]);
printf("%d, %d\n", *(p + 0), *(p + 1));
//不再使用分配的内存空间的时候,将这块内存空间释放
free(p);//释放在堆区中所分配的内存
//释放内存后,需要将指针p设置为空指针,避免野指针的出现
p = NULL;
return 0;
}
tarena@TNV:day21$ vim malloc.c
tarena@TNV:day21$ gcc malloc.c -o malloc
tarena@TNV:day21$ ./malloc
分配内存成功,首地址为:0x1e8c010
100, 200
100, 200
动态分配内存与释放 – calloc - clear allocation
- 分配数组并初始化
– #include <stdlib.h>
– void *calloc(size_t nmemb,size_t size);
– 从堆中分配包含nmemb个元素的数组,其中每个元素占size字节
– 成功返回该数组的起始地址,失败返回NULL
– 对所分配数组的每个元素,用相应类型的0初始化
//calloc函数 - 也是分配内存
// 1.数组
// 2.初始化为0
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *p = NULL;
p = (int *)calloc(4, sizeof(int));//连续分配4个元素的内存并且元素都为4个字节(int)
//返回内存的首地址赋值给p
if(p == NULL){
printf("clear allocation failed\n");
return -1;
}
printf("clear allocation success : %p\n", p);
//将p看作数组名
for(int i = 0; i < 4; i++){
p[i] = i + 100;
}
//打印数组的内存值
for(int i = 0; i < 4; i++){
printf("%d ",*(p + i));
}
printf("\n");
//调用free函数释放内存
free(p);
p = NULL;
return 0;
}
tarena@TNV:day21$ vim calloc.c
tarena@TNV:day21$ gcc calloc.c -o calloc
tarena@TNV:day21$ ./calloc
clear allocation success : 0x1db0010
100 101 102 103
动态分配内存与释放 – realloc - reset allocation - 调整分配内存的大小
- 调整动态内存大小
– #include <stdlib.h>
– void *realloc(void *ptr,size_t size);
– 将ptr所指向的动态内存大小调整为size字节,原内容保持不变,对新增部分不做初始化
– 成功返回调整后内存块的起始地址,失败返回NULL
– ptr必须是之前malloc/calloc/realloc函数的返回值
free
free函数的参数 - 就是malloc函数的返回值
calloc - clear allocation
realloc - reset allocation - 调整分配内存的大小
比较单独来使用该函数 - 将其和malloc / calloc 配套使用
void* realloc (void* ptr, size_t size);
ptr - 调整是ptr所指向的内存空间
size - 将ptr指向的内存空间调整为size个字节
int *p = (int *)malloc(8);
realloc(p,16);
将p指向的内存空间的字节数调整为16个字节 - 在原有的字节基础上向后面添加
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *p = NULL;
//在堆区中分配8个字节
p = (int *)malloc(8);
if(NULL == p){
printf("memory allocation failed \n");
return -1;
}
printf("分配内存成功,首地址为:%p\n", p);
//对内存进行处理
//...
*(p + 0) = 100;
*(p + 1) = 200;
printf("%d, %d\n",p[0], p[1]);
printf("%d, %d\n",*(p + 0), *(p + 1));
//调用realloc调整内存大小
printf("调用realloc调整内存大小\n");
p = realloc(p, 16);//在原先malloc分配的8字节内存后面继续额外多分配8字节内存
//返回整个内存的首地址给p
for(int i = 0; i < 4; i++) p[i] = i + 50;
for(int i = 0; i < 4; i++) printf("%d ", p[i]);printf("\n");
//不再使用分配的内存空间的时候,将这块内存空间释放
free(p);//释放在堆区中所分配的内存
//释放内存后,需要将指针p设置为空指针,避免野指针的出现
p = NULL;
return 0;
}
tarena@TNV:day21$ vim realloc.c
tarena@TNV:day21$ gcc realloc.c -o realloc
tarena@TNV:day21$ ./realloc
分配内存成功,首地址为:0x18ab010
100, 200
100, 200
调用realloc调整内存大小
50 51 52 53
动态分配内存与释放
- 调整动态内存大小
- 若ptr取NULL,则等价于malloc函数
– realloc(NULL,1024); - 若size取0,则等价于free函数
– 和free函数效果相同
员工信息
#include <stdio.h>
#include <stdlib.h>
#define NUM (4)
//想要存储NUM个员工的信息,分配NUM个emp_t类型的存储区
//声明描述员工信息的结构体类型
typedef struct employee{
char name[32];
int age;
}emp_t;
emp_t *get_employee_info(void){
emp_t *p =(emp_t *)malloc(sizeof(emp_t)*NUM);
emp_t *ptmp = p;//临时备份分配的内存首地址,将来用于返回
//完成员工信息的初始化
for(int i = 0; i < NUM; i++){
printf("请输入员工姓名:");
scanf("%s", p->name);//将输入的姓名直接放到name成员中
printf("请输入员工年龄:");
scanf("%d", &p->age);//将输入的年龄直接放到age成员中
p++;
}
//返回分配的内存的首地址
return ptmp;
}
int main(void){
//定义函数分配初始化员工信息的内存
emp_t *p = get_employee_info();
printf("\n打印员工的信息\n");
//打印员工信息
for(int i = 0; i < NUM; i++){
printf("%s, %d\n", p[i].name, p[i].age);
}
//释放内存
free(p);
return 0;
}
tarena@TNV:day21$ vim employee.c
tarena@TNV:day21$ gcc employee.c -o employee
tarena@TNV:day21$ ./employee
请输入员工姓名:lb
请输入员工年龄:13
请输入员工姓名:ww
请输入员工年龄:14
请输入员工姓名:sj
请输入员工年龄:52
请输入员工姓名:lb
请输入员工年龄:18
打印员工的信息
lb, 13
ww, 14
sj, 52
lb, 18
获取两点坐标,求出中心点位置
从键盘获取两个点的坐标,获取其中心点的位置
两个点
每个点 x y
#include <stdio.h>
#include <stdlib.h>
//声明描述一个点的坐标结构体
typedef struct point{
int r;
int c;
}pt;
//声明描述两个点的坐标的结构体
typedef struct{
pt p1;
pt p2;
}rect;
pt* midpt(const rect* p){
pt* pmid = (pt *)malloc(sizeof(pt));
if(pmid){
pmid->r = (p->p1.r + p->p2.r) / 2;//中心点的横坐标
pmid->c = (p->p1.c + p->p2.c) / 2;//中心点的纵坐标
}
return pmid;
}
int main(void){
rect r = {0};//定义了结构体变量,具有了两个点
printf("请输入两个点的坐标:");
scanf("%d%d%d%d", &(r.p1.r), &(r.p1.c), &(r.p2.r), &(r.p2.c));
pt* p_mid = midpt(&r);
if(p_mid){
printf("中心点的位置是(%d,%d)\n", p_mid->r, p_mid->c);
}
free(p_mid);
p_mid = NULL;
return 0;
}
tarena@TNV:day21$ vim mid.c
tarena@TNV:day21$ gcc mid.c -o mid
tarena@TNV:day21$ ./mid
请输入两个点的坐标:3 4 6 8
中心点的位置是(4,6)
获取两点坐标,求出长方形面积
练习 -
从键盘获取两个点的坐标, 以这两个点为对角的两个点的长方形,
计算这个长方形的面积.
#include <stdio.h>
#include <stdlib.h>
typedef struct{
int r;
int c;
}pt;
typedef struct{
pt p1;
pt p2;
}rect;
double area_p(const rect p){
return ((p.p1.r - p.p2.r) * (p.p1.c - p.p2.c));
}
int main(void){
rect r = {0};//定义了结构体变量,具有了两个点
printf("请输入两个点的坐标:");
scanf("%d%d%d%d", &(r.p1.r), &(r.p1.c), &(r.p2.r), &(r.p2.c));
double area = area_p(r);
printf("长方形的面积为:%lg\n", area);
return 0;
}
tarena@TNV:day21$ vim rectangle.c
tarena@TNV:day21$ gcc rectangle.c -o rectangle
tarena@TNV:day21$ ./rectangle
请输入两个点的坐标:0 0 5 5
长方形的面积为:25
11.文件操作
文件操作
打开文件后可以做的操作
读取
写入
操作流程:
创建打开a.txt, 向a.txt中写入: 1989 moive 3.14
1.从a.txt中将内容全部读取到变量中, 输入到屏幕上
int
字符串
double
2.将三个变量中的内容输出到b.txt文件中
i str d - fprintf
非格式化的输入输出
依次将c.txt中的内容读取, 写入到d.txt文件中
文件读写位置
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ---- ----
// ^
fwrite(a, sizeof(int), len, fp);
rewind(fp);
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ---- ----
//^
fread(b, sizeof(int), 9, fp);
fopen("c.txt", "a");
I/O流的打开
include <stdio.h>
FILE* fopen(
const char* path,//文件路径
const char* mode //打开模式
);
成功返回I/O流指针,作为后续I/O流函数的参数,失败返回NULL
之前的所有的代码 - 掉电丢失 - 存储在内存
运行的过程中出现的数据 - 存储到闪存 - 掉电数据不丢失
如果将数据存储到某个文件中 - 存储到磁盘中
如果程序从某个文件中读取数据 - 从磁盘中读取数据
对文件的操作
标准C库中 -
fopen - 打开一个文件
向文件中写入数据
从文件中读取数据
前提 - 打开文件
fopen("./a.txt", "r");
fclose - 关闭一个文件
将文件关闭
fp = fopen("b.txt", "r");//只读方式,存在读取,不存在失败
if(fp){
printf("open success\n");
}
printf("open failed\n");
打开模式
struct _iobuf{
char *_ptr; //文件输入的下一个位置
int _cnt; //当前缓冲区的相对位置
char *_base;//指基础位置(文件的起始位置)
int _flag; //文件标志
int _file;//文件的有效性验证
int _charbuf;//检查缓冲区状况
int _bufsiz;//文件的大小
char *_tmpfname;//临时文件名
};
typedef struct _iobuf FILE;
stdio.h - 声明
I/O流的关闭
int fclose(
FILE* fp // I/O流指针
);
成功返回0,失败返回EOF
//文件操作的标准C库函数
#include <stdio.h>
int main(void){
FILE* fp = NULL;
//fp = fopen("a.txt", "w");//只写方式,存在清空,不存在创建新文件
fp = fopen("b.txt", "r");//只读方式,存在读取,不存在失败
if(fp == NULL){
printf("open failed\n");
return -1;
}
printf("open sunccess\n");
//关闭文件
fclose(fp);
fp = NULL;//好习惯
return 0;
}
tarena@TNV:day21$ vim file.c
tarena@TNV:day21$ gcc file.c -o file
tarena@TNV:day21$ ./file
open sunccess
tarena@TNV:day21$ vim file.c
tarena@TNV:day21$ gcc file.c -o file
tarena@TNV:day21$ ./file
open failed
系统每个进程缺省打开三个标准I/O流
格式化输出
格式化输入
#include <stdio.h>
int main(void){
FILE* fp = NULL;
//打开文件
fp = fopen("a.txt", "r");//以只读方式打开当前目录下的a.txt
if(NULL == fp){
printf("file open failed\n");
return -1;
}
//定义变量存储从文件中读取的内容
int i = 0;
char str[20] = {0};
double d = 0;
//按照一定的格式从a.txt文件中读取数据保存到变量i, str, d中
fscanf(fp, "%d%s%lg", &i, str, &d);
printf("i = %d, str[20] = %s, d = %lg\n", i, str, d);
//关闭文件
fclose(fp);
fp = NULL;
FILE* fpw = fopen("b.txt", "w+");//可读可写方式打开b.txt
//按照指定的格式将变量i , str, d中的数据保存到文件b.txt中
fprintf(fpw, "%d %s %lg", i, str, d);
//关闭文件
fclose(fpw);
fpw = NULL;
//从标准输入设备键盘上获取数据保存到i,str,d中
fscanf(stdin, "%d%s%lg", &i, str, &d);
//将i,str,d打印输出到标准输出设备显示器上
fprintf(stdout, "%d,%s,%lg\n", i, str, d);
//将i,str,d打印输出到标准输出设备显示器上
fprintf(stderr, "%d,%s,%lg\n", i, str, d);
return 0;
}
tarena@TNV:day22$ vim file.c
tarena@TNV:day22$ gcc file.c -o file
tarena@TNV:day22$ ./file
i = 1989, str[20] = movie, d = 3.14
2022 sunny 5.67
2022 sunny 5.67
2022 sunny 5.67
tarena@TNV:day22$ cat a.txt
1989 movie 3.14
tarena@TNV:day22$ cat b.txt
1989 movie 3.14
fgetc 和 fputc - 格式化输入输出
//实验步骤:
//touch c.txt
//echo 1233456789abcdef > c.txt
#include <stdio.h>
int main(void){
FILE* fpr = fopen("c.txt", "r");
if(NULL == fpr){
printf("file open failed\n");
return -1;
}
FILE* fpw = fopen("d.txt", "w+");
if(NULL == fpw){
printf("file open failed\n");
return -1;
}
int ch;
while(1){
ch = fgetc(fpr);//从c.txt文件中读取一个字符保存到ch变量中
if(EOF == ch)//判断遇到了错误,或者到了文件末尾
break;
fputc(ch, fpw);//向d.txt文件中写入一个字符
}
//关闭文件
fclose(fpr);
fpr = NULL;
fclose(fpw);
fpw = NULL;
return 0;
}
tarena@TNV:day22$ ls
a.txt b.txt file file.c
tarena@TNV:day22$ touch c.txt
tarena@TNV:day22$ echo 1233456789abcdef > c.txt
tarena@TNV:day22$ cat c.txt
1233456789abcdef
tarena@TNV:day22$ vim File2.c
tarena@TNV:day22$ gcc File2.c -o File2
tarena@TNV:day22$ ./File2
tarena@TNV:day22$ ls
a.txt b.txt c.txt d.txt file File2 File2.c file.c
tarena@TNV:day22$ cat d.txt
1233456789abcdef
fputs 和 fgets - 非格式化输入输出
//实验步骤:
//touch e.txt
//echo 1233456789abcdef > e.txt
#include <stdio.h>
int main(void){
FILE* fpr = fopen("e.txt", "r");
if(NULL == fpr){
printf("file open failed\n");
return -1;
}
FILE* fpw = fopen("f.txt", "w+");
if(NULL == fpw){
printf("file open failed\n");
return -1;
}
//fgets和fputs
char str[4] = {0};//暂存到文件中读取的字符串
while(fgets(str, 4, fpr) != NULL){//循环从文件e.txt中读取3个字符
fputs(str, fpw);//将读取到的数据写入到文件f.txt中
}
//关闭文件
fclose(fpr);
fpr = NULL;
fclose(fpw);
fpw = NULL;
return 0;
}
tarena@TNV:day22$ ls
a.txt b.txt c.txt d.txt file File2 File2.c file3 file3.c file.c
tarena@TNV:day22$ touch e.txt
tarena@TNV:day22$ echo 1233456789abcdef > e.txt
tarena@TNV:day22$ gcc file3.c -o file3
tarena@TNV:day22$ ./file3
tarena@TNV:day22$ ls
a.txt b.txt c.txt d.txt e.txt file File2 File2.c file3 file3.c file.c f.txt
tarena@TNV:day22$ cat f.txt
1233456789abcdef
tarena@TNV:day22$ cat e.txt
1233456789abcdef
二进制I/O – fwrite和fread
#include <stdio.h>
int main(void){
FILE* fpw = fopen("m.txt", "w+");
if(NULL == fpw){
printf("file open failed\n");
return -1;
}
int a[] = {1,2,3,4,5,6,7,8,9,10};
int len = sizeof(a) / sizeof(a[0]);
int size;
//向文件m.txt中写入len个数据,每个数据4字节
size = fwrite(a, sizeof(int), len, fpw);
printf("实际写了%d个数据\n", size);
//从文件m.txt中读取数据
int b[8] = {0};//定义数组存储文件m.txt中读取到的内容
size = fread(b, sizeof(int), 8, fpw);//想要从文件m.txt中读取到9个int类型数据存储到数组b中
printf("实际读取了%d个数据\n", size);
//关闭文件
fclose(fpw);
fpw = NULL;
return 0;
}
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ ls
a.txt b.txt c.txt d.txt e.txt file File2 File2.c file3 file3.c file4.c file.c f.txt
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了10个数据
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了10个数据
实际写了0个数据
#include <stdio.h>
int main(void){
FILE* fpw = fopen("m.txt", "w+");
if(NULL == fpw){
printf("file open failed\n");
return -1;
}
int a[] = {1,2,3,4,5,6,7,8,9,10};
int len = sizeof(a) / sizeof(a[0]);
int size;
//向文件m.txt中写入len个数据,每个数据4字节
size = fwrite(a, sizeof(int), len, fpw);
printf("实际写了%d个数据\n", size);
rewind(fpw);
//从文件m.txt中读取数据
int b[8] = {0};//定义数组存储文件m.txt中读取到的内容
size = fread(b, sizeof(int), 9, fpw);//想要从文件m.txt中读取到9个int类型数据存储到数组b中
printf("实际读取了%d个数据\n", size);
for(int i = 0; i < size; i++){
printf("%d ",b[i]);
}
printf("\n");
//关闭文件
fclose(fpw);
fpw = NULL;
return 0;
}
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了10个数据
实际读取了9个数据
1 2 3 4 5 6 7 8 9
文件位置与随机访问 – fseek和ftell
#include <stdio.h>
int main(void){
FILE* fpw = fopen("m.txt", "w+");
if(NULL == fpw){
printf("file open failed\n");
return -1;
}
int a[] = {1,2,3,4,5,6,7,8};
int len = sizeof(a) / sizeof(a[0]);
int size;
//向文件m.txt中写入len个数据,每个数据4字节
size = fwrite(a, sizeof(int), len, fpw);
printf("实际写了%d个数据\n", size);
rewind(fpw);
//从文件m.txt中读取数据
int b[8] = {0};//定义数组存储文件m.txt中读取到的内容
size = fread(b, sizeof(int), 8, fpw);//想要从文件m.txt中读取到8个int类型数据存储到数组b中
printf("实际读取了%d个数据\n", size);
for(int i = 0; i < size; i++){
printf("%d ",b[i]);
}
printf("\n");
//fseek函数演示
int c[2] = {0};
fseek(fpw, 8, SEEK_SET);//从文件头开始往后数8个字节的位置开始操作文件
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ----
// ^
fread(c, sizeof(int), 2, fpw);
printf("%d, %d\n", c[0], c[1]);
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ----
// ^
//读取完毕后,文件位置调整到了5的位置上
fseek(fpw, 8, SEEK_CUR);//从当前文件位置向后移动8个字节的位置开始文件操作
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ----
// ^
fread(c, sizeof(int), 2, fpw);
printf("%d, %d\n", c[0], c[1]);
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ---- ----
// ^
//读取完毕后,文件位置调整到了文件末尾
fseek(fpw, -12, SEEK_END);//从文件末尾向前调整12个字节的位置开始操作文件
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ---- ----
// ^
fread(c, sizeof(int), 2, fpw);
printf("%d, %d\n", c[0], c[1]);
//1 2 3 4 5 6 7 8
//---- ---- ---- ---- ---- ---- ---- ---- ----
// ^
rewind(fpw);
int ch[10] = {0};
size = fread(ch, sizeof(int), 10, fpw);
printf("实际读取了%d个字节\n", size);
for(int i = 0; i < size; i++){
printf("%d ", ch[i]);
}
printf("\n");
printf("当前的读写位置为:%ld\n", ftell(fpw));
//关闭文件
fclose(fpw);
fpw = NULL;
return 0;
}
tarena@TNV:day22$ vim file4.c
tarena@TNV:day22$ gcc file4.c -o file4
tarena@TNV:day22$ ./file4
实际写了8个数据
实际读取了8个数据
1 2 3 4 5 6 7 8
3, 4
7, 8
6, 7
实际读取了8个字节
1 2 3 4 5 6 7 8
当前的读写位置为:32