岑溪网站开发,体育类网站模板,外贸软件销售好做吗,网页设计基础心得体会C语言学习
十.指针详解
6.有关函数指针的代码
代码1#xff1a;(*(void (*)())0)(); void(*)()是函数指针类型#xff0c;0是一个函数的地址 (void(*)())是强制转换 总的是调用0地址处的函数#xff0c;传入参数为空 代码2#xff1a;void (*signal(int, void(*)(int))…C语言学习
十.指针详解
6.有关函数指针的代码
代码1(*(void (*)())0)(); void(*)()是函数指针类型0是一个函数的地址 (void(*)())是强制转换 总的是调用0地址处的函数传入参数为空 代码2void (*signal(int, void(*)(int)))(int); (int, void(*)(int))是函数的传参列表 signal是函数名 剩下的是函数的返回值类型 这段代码可以简化为
typedef void(*pfun_t)(int); //命名一个函数返回类型
pfun_t signal(int,pfun_t);
以上四种输出都等价
7.函数指针数组
定义一个函数指针数组int (*parr[4])(int, int) {Add, Sub, Mul, Div}; 函数指针数组的作用转移表 int(*(*ppfArr) [4])(int, int) \pfArr;ppfArr是一个数组指针指针指向的数组有4个元素每个元素的类型是一个函数指针int(*)(int, int)
8.回调函数
定义回调函数就是一个通过函数指针调用的函数。如果你把函数的指针地址作为参数传递给另一个函数当这个指针被用来调用其所指向的函数时我们说这是回调函数 例子stdlib中的qsort函数qsort是一个库函数内部用函数指针调用了一个函数 语法qsort(数组起始地址, 数组长度, sizeof元素, 函数指针自定义的比较方法) 比较方法的例子
int compare(const void* e1, const void* e2){return *(int*)e1 - *(int*)e2;
}当返回的值小于0时表示第一个元素应该排在第二个元素之前所以上述代码是升序 其中void*类型的指针可以接收任意类型的地址但是void*类型的指针不能进行解引用操作和加减整数的操作 因为不能进行解引用操作所以想要拿到e1中的数据就要先强转换再解引用 注意compare函数传入的是指针如果是结构体调用元素要用-而不是.
指针题目
①int a[] { 1, 2, 3, 4 }; sizeof(a0)a是首元素地址得到的是首元素的大小 sizeof(a)a取出的是数组的地址但是数组的地址也是地址也就是4/8个字节 sizeof(*a)a取得是数组的地址*解的是数组的地址所以得到的是16 ②char a[] { ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’}; strlen(arr)字符串数组以\0为结尾a中没有所以结果是随机值 strlen(arr0)也是随机值理由同上 strlen(*arr)会报错字符存储时存储的是ASCII码传给strlen的是a的ASCII码所以会报错 ③char a[] “abcdef”; sizeof(arr)sizeof计算的是数组的大小所以是7 sizeof(arr0)4/8计算的是地址的大小 ④char *p “abcdef”;p存储的是a的地址 sizeof(*p)1*p就是字符串的第一个字符 sizeof(p[0])p[0] *(p0)所以是1 ⑤int a[3][4] { 0 }; sizeof(a 1)首元素是第一行a1是第二行的地址所以是4 sizeof(a[3])不会报错因为sizeof不会真的访问第四行只会得到a[3]的类型所以是16 ⑥int a[5] { 1, 2, 3, 4, 5 }; int *ptr (int *)(a 1); *(ptr - 1)a是数组的地址a1得到一个地址后再减一就是5的地址 ⑦
p 0x1因为Test类型的变量大小是20字节转为16进制后是14所以是0x00100014 (unsigned long)p 0x1强转为整型后p0x1相当于p1整型大小为4个字节结果为0x00100004
其中(0, 1)等都是逗号表达式取最后一个数字 所以p[0] 1
首先会报一个警告因为p的长度和a的长度不一样 p[4][2]等价于*(*(p4)2)所以p[4][2]就是a[3][3] 地址相减是-4但是%p是把-4的补码的直接值打印出来所以是0xFFFFFFFC
a是字符指针的数组存放着三个字符串的首元素的地址 pa是a数组的地址也就是首元素的地址 pa使其指向第二个元素所以输出的是at
unsigned char的范围是0-255ab超过范围c会变成44
str1和str2的地址不同所以不等于 str3和str4都指向一个常量字符串所以地址相等
十一.字符函数和字符串函数
大多字符串函数都要引入头文件string.h
1.strcat
语法char *strcat(str1, str2); 作用将str2追加到str1后面返回一个指向结果字符串的指针 缺点不能将自己添加到自己后面 注意事项
str2中必须包含\0str1必须足够大str1必须可修改
2.strncat
语法char *strncat(str1, str2, size); size是要追加的最大字符串 作用将str2的size个字符追加到str1后面返回一个指向结果字符串的指针
3.strstr
注意该函数要引入的头文件是stdio.h而不是string.h 语法char *strstr(str1, str2); 作用在str1中查找str2的第一次出现位置。该函数返回指向找到的子字符串的指针如果未找到则返回NULL
4.strlen
语法size_t strlen(const char* str); 作用返回一个字符串从头到\0的字符的个数\0不算进长度 注意size_t就是一个被重命名的unsigned int无符号数字永远大于0所以size_t跟size_t相减的结果也只会是正数
5.strcpy
语法char *strcpy(str1, str2); 作用将str2中的字符按顺序一个一个替换str1中的字符注意str2中的\0也会替换一个字符返回结果字符串的指针 注意事项
str2中必须有\0str1必须足够大str1必须可修改
6.strncpy
语法char *strcpy(str1, str2, size); size是要拷贝的字符串的最大长度 将str2中的字符按顺序一个一个替换str1中的字符替换size次注意str2中的\0也会替换一个字符返回结果字符串的指针
7.strcmp
语法int strcmp(str1, str2); 作用比较两个字符串的ASCII码若str1\str2则返回一个小于零的数字若str1str2则返回一个等于零的数字若str1str2则返回一个大于零的数字
8.strtok
语法char *strtok(str, const char *sep); sep是个字符串定义了用分隔符的字符集合 作用根据分隔符切割一次str字符串返回一个子字符串的指针如果str是NULL则从上一次调用后的位置继续分割字符串 注意strtok会直接修改传入的字符串所以str一般是字符串的拷贝 使用例
#include stdio.h
#include string.hint main() {char str[] Hello world, how are you?;char *p ;for (char *ret strtok(arr, p); ret ! NUll; ret strtok(NULL, p)) {printf(%s\n, token);}return 0;
}输出结果为
Hello
world,
how
are
you?9.strerror
语法char *strerror(错误码); 作用根据错误码打印错误信息错误码一般是变量errno errno是头文件errno.h中的一个全局的错误码的变量当C语言的库函数在执行时发生了错误就会把对应的错误码赋值到errno中
10.字符判断函数 11.字符转换函数
函数tolower()和toupper 作用转换大小写支持ASCII码转换
十二.内存函数
1.memcpy
语法void *memcpy(void *destination, const void *source, int size); 作用将一个任意类型的数组source拷贝到destination中最多size个元素 注意事项
destination和source不能有内存重叠destination和source类型要相同destination的长度要大于source的长度
2.memmove
语法void *memcpy(void *destination, const void *source, int num); 作用将一个任意类型的数组source拷贝到destination中最多size个元素 与memcpy的区别在C语言标准中memcpy只能处理不重叠的内存某些编译器中可以处理重叠的memmove用于处理重叠内存的拷贝
3.memcmp
语法int memcmp(const void *p1, const void *p2, num); 作用比较内存大小若p1\p2则返回一个小于零的数字若p1p2则返回一个等于零的数字若p1p2则返回一个大于零的数字一共比较size个字节
4.memset
语法void *memset(void *destination, int c, size_t count); c是要设置的字符count是一个无符号数字是要设置为指定值的字节数 作用内存设置将指定的内存区域设置为指定的值常用于初始化数组或结构体等数据结构 注意点
memset函数是按字节进行设置的因此在设置非字符类型比如整型的数组时可能会导致数据不符合预期特别是在涉及到字节序的情况下在使用memset函数时要确保不会越界访问内存否则会导致未定义的行为memset函数的性能通常很高因为它可以利用处理器的特殊指令进行优化
十三.结构体
1.基本使用
声明语法
struct stu{成员列表;
}结构体变量列表; //最后的分号不能丢创建一个结构体变量struct stu s1; 初始化方法struct stu s1 {成员变量的值};
声明的特殊写法
struct{memberList;
}x;匿名结构体类型x是唯一的结构体变量
struct{}* p;这样写p就是结构体指针
结构体的自引用
struct student{struct student n;
};这种自引用的写法的内存无法判断运行就会报错 正确的写法
struct student{struct student *p;
};这种写法的结构体内存是确定的所以可以这么写 写法2
typedef struct student{ //不能写成匿名结构体struct student *p;
}student;2.结构体内存对齐
结构体的对齐规则
第一个成员在与结构体变量偏移量为0的地址处其它成员变量要对齐到对齐数的整数倍的地址处对齐数编译器默认的一个对齐数与该成员变量大小中的较小值VS编译器的默认对齐数是8编译器可能没有默认对齐数结构体的总大小为最大对齐数的整数倍如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍
内存对齐存在的理由
不是所有平台都能访问任意地址上的任意数据某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常数据结构尤其是栈应该尽可能在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要作两次内存访问而对其的内存访问仅需一次
总的来说内存对齐是拿空间换时间的做法 修改默认对齐数
#pragma pack(对齐数) //设置默认对其数
代码
#pragma pack() //取消设置的默认对齐数可以不写但是对齐数就永远都会是设置的对齐数计算偏移量size_t offsetof(结构体名, 成员变量名);
3.结构体传参
将结构体传给函数
void Init(struct stu *s1){s1-name a;s1-sex 1;
}
int main(){struct stu s1 {0};Init(s1); //最好传地址return 0;
}4.位段
位段的声明和结构体是类似的有两个不同
位段的成员必须是int、unsigned int、signed int位段的成员名后边有一个冒号和一个数字
位段的内存分配
位段的成员可以是int、unsigned int、signed int和char属于整型家族位段的空间是按照需要以4个字节int或者1个字节char的方式来开辟的位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段
位段的跨平台问题
总结跟结构体比位段可以达到同样的效果但是更省空间但是有跨平台的问题 位段的应用
5.枚举
枚举类型的声明
enum sex{man,woman,helicopter
};创建一个枚举变量enum sex s man; 枚举的优点
可读性和可维护性强防止命名污染和#define定义的标识符比较枚举有类型检查更加严谨便于调试使用方便一次可以定义多个常量
6.联合
联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员特征是这些成员公用同一块空间所以联合也叫共用体 当我们修改其中一个成员时其他成员的值也会发生变化因为它们共享同一块内存空间 联合的内存计算联合的大小至少是最大成员的内存最终会是对齐数的整数倍 联合的声明
union S{...
};判断大小端存储模式
int check_sys(){union{char c;int i;}u;u.i 1;return u.c; //1是小端0是大端
}十四.内存动态分配
1.malloc
语法void malloc(size_t); size_t是要申请获取的字节大小 注意malloc的返回值是void使用时最好强制转换一下 使用
#include errno.h
#include string.h
#include stdlib.h //malloc和free都是声明在stdlib.h中的
#include stdio.h
int main(){int *p (int*)malloc(20); //申请5个整型的空间if (p NULL){printf(%s\n, strerror(errno)); //如果请求失败则返回空指针}else{for (int i 0; i 5; i){*(p i) i;}}return 0;
}2.free
语法void free(void* p) 作用释放空间将分配给指针p的空间返回栈区p指针不会重置为NULL 注意
malloc在程序结束时也会自动释放空间free是主动释放、如果参数p指向的空间不是动态开辟的那free函数的行为是未定义的若果参数p是NULL指针则函数什么都不做
3.calloc
作用开辟一个数组空间并初始化为0 语法void *calloc(size_t num, size_t size); num是元素个数size是一个元素的大小 如果开辟失败则返回NULL指针 和malloc的区别初始化为0
4.realloc
作用调整动态开辟的空间的大小 语法void *realloc(void *空间指针, size_t newSize); newSize是新的空间大小 注意事项
如果p指向的空间之后有足够的内存空间可以追加则直接追加后返回p如果p指向的空间之后没有足够的内存空间可以追加则返回一个新的地址建议用一个新的变量来接受realloc函数的返回值
常见的动态内存错误
①对空指针解引用 ②对动态开辟内存的越界访问 ③对非动态开辟的空间使用free ④使用free释放动态内存的一部分 ⑤对同一块动态内存的多次释放 ⑥动态开辟内存忘记释放内存泄露
经典的笔试题
①
输出结果是程序崩溃原因是str是将自己的值而不是自己的地址传给GetMemory函数也就是说p是str的一份拷贝给p赋值不会传递给strstr始终是空指针 其次这段代码中有内存泄露的问题
返回栈空间的地址的问题str指向p但是p在执行完函数后就会被销毁于是str指向未知地址 所以不能返回栈空间地址可以用static修饰但是堆空间不会被及时销毁所以也可用malloc分配空间
5.柔性数组
C99规定结构中的最后一个元素允许是未知大小的数组这就叫柔性数组成员 例
struct S{int n;int arr[0]; //未知大小的柔性数组成员
};
int main(){struct S* ps (struct S*)malloc(sizeof(struct S)5*sizeof(int)); //第一个sizeof不会计算柔性数组的大小结果是4if (ps ! NULL) { // 检查内存是否成功分配ps-arr[0] 0; // 将arr的第一个元素赋值为0// 在程序结束前释放动态分配的内存free(ps);}return 0;
}连续内存访问效率高比如上述代码中的n和arr 柔性数组的特点
结构中的柔性数组成员前面必须至少有一个其它成员sizeof返回的这种结构大小不包括柔性数组的内存包含柔性数组成员的结构用malloc函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小
优点方便内存释放访问速度高
十五.文件操作
1.文件
文件分为程序文件和数据文件 数据文件根据数据的组织形式数据文件被称为文本文件ASCII码或二进制文件 文件缓冲区ANSIC标准采用缓冲文件系统处理数据文件的所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块文件缓冲区 缓冲区分输出缓冲区和输入缓冲区缓冲区的大小由C编译系统决定 文件指针文件类型指针 每个被使用的文件都在内存中开辟了一个相应的文件信息区用来存放文件的相关信息。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的为FILE 一般都是通过一个FILE的指针来维护这个FILE结构的变量FILE *pf;
2.文件的打开和关闭
ANSIC规定使用fopen函数来打开文件fclose关闭文件都是stdio中的函数 语法 FILE *fopen(const char *文件名, const char *打开方式); int fclose(FILE *stream); 打开方式
文件的顺序读写
fgetc的语法int fgetc(FILE *stream);如果stream是stdin则表示从键盘读取文本行fgets同理 fputc的语法int fputc(int character, FILE *stream);character参数是要写入的字符以ASCII码形式表示 fgets的语法char *fgets(char *str, int n, FILE *stream);n表示要读取的最大字符数返回的是指向str的指针 fputs的语法int fputs(const char *str, FILE *stream); fscanf的语法int fscanf(FILE *stream, const char *format, …);根据指定的格式字符串从文件中读取数据format是%s之类的 fprintf的语法int fprintf(FILE *stream, const char *format, …);根据指定的格式字符串将数据写入到文件中 fread的语法size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);(ptr是存储数据的指针size是每个数据项的大小nmemb是要读取或写入的数据项的数量返回的指针指向ptr) fwrite的语法size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
几个scanf和printf的对比
scanf 用于从标准输入键盘读取输入。 格式化输入函数可以根据格式字符串从标准输入中读取数据。 例子scanf(“%d”, num); printf 用于向标准输出屏幕输出内容。 格式化输出函数可以按照格式字符串将数据输出到屏幕。 例子printf(“The value of num is %d”, num); fscanf 用于从文件中读取输入。 格式化输入函数可以根据格式字符串从文件中读取数据。 例子fscanf(file_ptr, “%d”, num); fprintf 用于向文件中写入内容。 格式化输出函数可以按照格式字符串将数据输出到文件中。 例子fprintf(file_ptr, “The value of num is %d”, num); sscanf 用于从字符串中读取输入。 格式化输入函数可以根据格式字符串从字符串中读取数据。 例子sscanf(str, “%d”, num); sprintf 用于将格式化的数据写入字符串中。 格式化输出函数可以按照格式字符串将数据输出到字符串中。 例子sprintf(str, “The value of num is %d”, num); 总的来说scanf、printf用于标准输入输出fscanf、fprintf用于文件输入输出sscanf、sprintf用于字符串处理
3.其它函数
(1)fseek
语法int fseek(FILE *stream, long offset, int origin);stdio.h中的 offset是偏移量origin是文件指针的当前位置 作用将文件指针从origin这个位置偏移offset个字节 一些常量
SEEK_CUR文件指针的当前位置SEEK_END文件的末尾位置SEEK_SET文件起始位置
(2)ftell
语法long int ftell(FILE *stream); 作用获取stream的相对于文件开头的偏移量
(3)fwind
语法void rewind(FILE *stream); 作用将stream重新指向文件开头
(4)文件结束判定
feof函数的语法int feof(FILE *stream); feof函数的作用在文件读取结束的时候判断是读取失败还是遇到文件结尾结束如果失败则返回0如果遇到文件结尾则返回非负值 函数perror(str);可以打印str报错信息且是stdio中的函数比sterror(errno)方便
十六.程序环境和预处理
1.翻译环境和运行环境
在ANSI C的任何一种实现中存在两个不同的环境 第一个是翻译环境在这个环境中源代码被转换为可执行的机器指令 第二个执行环境它用于实际执行代码 每个源文件都会被编译器处理编译成目标文件(add.c-add.obj) 然后目标文件通过链接器链接成可执行文件(add.obj-add.exe) 编译又分为三个阶段
预编译文本操作将include引入的头文件展开成代码并把注释删除使用空格代替注释替换#define的文本编译把c语言代码翻译成汇编代码(语法分析、词法分析、语义分析、符号汇总)汇编