一.数据在内存中的存储
1.1数据类型介绍
基本数据的类型:  类型的分类: 整形: 1.char也算到整型家族里面,因为字符在底层存储的时候,存储的是字符所对应的ASCII值(整数) 2.[int] 可以省略 unsigned (无符号) signed(有符号) 3.有符号signed的最高位为符号位,1表示负数,0表示正数, unsigned均为正数,最高位1是实数位,不为符号位。 浮点数类型:float double 构造类型:数组类型,结构体类型:struct 枚举类型:enum 联合类型:union 指针类型:int *pi,char *pc,float *pf,void *pv 空类型:void表示空类型,通常适用于函数的返回类型,函数的参数,指针类型。
1.2 整形在内存中的存储
计算机中的有符号数有三种表示方法,即原码、反码和补码。 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示"负”,而数值位三种表示方法各不相同。 原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码 + 1就得到补码。 整数有两种,有符号数和无符号数 有符号数-- - 符号位 + 数值位 正数 0 + 数值位 负数 1 + 数值位 举例说明: int b = -1; //10000000 00000000 00000000 00000001 - 原码 //11111111 11111111 11111111 11111110 - 反码 //11111111 11111111 11111111 11111111 - 补码 //ff ff ff ff - 十六进制显示形式 int a = 3; //00000000 00000000 00000000 00000011 - 原码、反码、补码 //0000 0000 0000 0000 0000 0000 0000 0000 0000 0011 //0 0 0 0 0 0 0 0 0 3 // 00 00 00 03 对于整形来说:数据存放内存中其实存放的是补码
1.3大小端字节序介绍
大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中; 小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位, , 保存在内存的高地址中。
1.4浮点型在内存中的存储解析
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式: (-1) ^ S* M * 2 ^ E (-1) ^ s表示符号位,当s = 0,V为正数﹔当s = 1,V为负数。 M表示有效数字,大于等于1, 小于2。 2 ^ E表示指数位。 IEEE754规定 : 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。  对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。  另外:IEEE754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
二.指针详细介绍
1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。 2.指针的大小是固定的4\8个字节(32位平台\64位平台) 3.指针是有类型,指针的类型决定了指针的+ -整数的步长,指针解引用操作时候的权限 4.指针的运算
2.1字符指针
在指针的类型中我们知道有一种指针类型为字符指针char*;  将字符串的首地址放到指针中,通过指针可以找到该字符串(不是将字符串内容放到指针里面去)  栈区:局部变量,函数形参,函数调用 堆区:动态内存如malloc等申请使用 静态区:全局变量,static修饰的局部变量 常量区:常量字符串 常量区中的内容在整个程序的执行期间是不允许被修改的,且同一份常量字符串只会创建一份,不会重复创建存储。
#include<stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
char* p1 = "abcdef";
char* p2 = "abcdef";
if (arr1 == arr2)
{
printf("arr1 == arr2\n");
}
else
{
printf("arr1 != arr2\n");
}
if (p1 == p2)
{
printf("p1 == p2\n");
}
else
{
printf("p1 != p2\n");
}
return 0;
}
运行结果:arr1 != arr2 ;p1 == p2 创建数组需要开辟空间,数组arr1和arr2在内存空间所在位置是不同的,所以arr1 != arr2; char p1 = “abcdef”; char p2 = “abcdef”; "abcdef"是常量字符串,不能被修改,在内存空间所占位置固定,char * p1 = “abcdef”; 是将该常量字符串的首地址放到字符指针p1中,char* p2 = “abcdef”; 是将该常量字符串的首地址放到字符指针p2中。也就是说p1和p2存放都是常量字符串"abcdef"的首地址,所以 p1 ==p2。(注意:同样的常量字符串只会存一份,不会同时存两份,所以不会开辟不同的空间来存储。)** const char p2 = “abcdef”; (指向常量字符串的指针最好加上const!)*
2.2指针数组
指针数组是一个存放指针的数组 例如: int* arr1[5]; char* arr2[5]; double* arr3[5];
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = { &a, &b, &c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *arr[i]);
}
return 0;
}
运行结果:10 20 30
2.3数组指针
数组指针是指针
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;
return 0;
}
进一步介绍数组指针
char arr[5];
char(*pa)[5] = &arr;
char* ch[8];
char* (*pc)[8] = &ch;
int(*p)[10];

int* p1;
char* p2;
int(*p3)[10];
对一个存放数组地址的指针进行解引用操作,找到的是这个数组,也就是这个数组的数组名,数组名这时候又表示数组首元素地址!
- (p + i):相当于拿到了一行,相当于这一行的数组名
(*p + i)[j] <===> ((p + i) + j)
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
printf("%d ", *(arr + i));
printf("%d ", arr[i]);
printf("%d ", p[i]);
}
return 0;
}
总结:我们对一个数组指针变量进行解引用操作,比如int(*p)[10],得到的是一个数组,或者说是这个数组的数组名,而数组名又可以表示该数组首元素的地址。如果要找到该数组中的每一个元素,就需要对这个数组元素的地址进行解引用操作。简单点来说就是,对一个数组指针类型进行解引用操作,得到的还是地址,对这个地址在进行相应的解引用操作,才能得到数组中的具体的元素。
2.4数组传参和指针传参
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int* arr)
{}
void test2(int* arr[20])
{}
void test2(int** arr)
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
以上五种传参方式均ok 注意:一维数组传参可以传数组形式,也可以传指针形式,传数组形式的时候数组元素的个数可以不写,也可以写,传指针的时候要注意指针的类型,也就是指针指向什么类型的元素,比如说指针指向int类型元素,那么指针的类型就是int 。*
2.5函数指针
我们创建函数的时候,就会在内存中开辟一块空间,既然占用了内存空间,那就有对应的内存空间地址。 函数指针,顾名思义就是指向函数的指针。  注意: & 函数名 和 函数名均表示函数的地址! 数组名 != &数组名 函数名 == &函数名 通过函数指针,我们可以找到函数,然后去调用这个函数。 函数指针是 & 函数名,而我们函数调用的时候可以直接使用函数名,那么这里通过函数指针调用函数也可以这样写: 
2.6函数指针数组
把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢 ? int (parr1[10])(); int parr210; int ()() parr3[10]; parr1 先和[]结合,说明parr1是数组。 数组的内容是什么呢 ? 是int()()类型的函数指针。
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int (*pa)(int, int) = Add;
int (*p[4])(int, int) = { Add,Sub,Mul,Div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", (*p[i])(2, 3));
}
return 0;
}
运行结果: 
2.7指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
char* arr1[5] = { 0 };
char* (*p1)[5] = &arr1;
int(*p2)(int, int) = Add;
int(*p3[4])(int, int) = { 0 };
int(*(*p4)[4])(int, int) = &p3;
return 0;
}
2.8回调函数
回调函数就是一个通过函数指针调用的函数。 理解:如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这个函数是回调函数。 特点:回调函数不是由该函数的实现方直接调用(其实也就是回调函数自身),而是在特定的事件或条件发生时由另外的一方调用的(另一个函数调用),用于对该事件或条件进行响应。
三.字符函数和字符串函数
C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
strlen 求字符串长度的算法分析:strlen接收到字符串起始位置的地址时,比较该地址处的内容是否为’\0’,若不为’\0’, 字符串的长度 + 1。  函数介绍:strlen size_t strlen(const char* str); 头文件:string.h 函数名:strlen 函数参数:str,参数类型是const char* ,即需要进行求字符串长度的起始地址 函数返回类型: size_t,size_t是unsigned int的类型重定义,是无符号整型。库函数使用size_t类型可能考虑的是字符串的长度不可能是负数,所以用了无符号类型size_t。 函数功能:计算字符串的长度 strcat 如果我们要将一个字符串的内容追加到另外一个字符串的末尾空间中时,需要使用字符串拷贝-- - strcat函数 函数介绍:strcat char* strcat(char* destination,const char* source); 头文件:string.h 函数名:strcat 函数参数: 参数1:destination, 类型:char* ,表示将字符串追加的目的地位置 参数2:source,类型:char* ,表示被追加字符串的源地址起始位置。 函数返回类型: char*,实际上就是返回destination(目的地)的起始位置 函数功能:字符串追加
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* dest_start = dest;
while (*dest != '\0')
{
dest++;
}
while (*dest++ = *src++)
{
;
}
return dest_start;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
my_strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
strcmp 如果我们要比较两个字符串的是否相等,或者比较字符串大小,不能用操作符 == 来直接进行判断,而是需要用到字符串比较函数strcmp strcmp函数进行字符串追加的算法分析: 函数介绍:strcmp int strcmp(const char* str1,const char* str2); 头文件:string.h 函数名:strcmp 函数参数: 参数1:str1, 类型:char* ,表示将进行比较的第一个字符串 参数2:参数2:str2, 类型:char* ,表示将进行比较的第二个字符串 函数返回类型: int, 返回两个字符串比较的结果 函数功能:字符串比较
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* p1, const char* p2)
{
assert(p1 && p2);
while (*p1 == *p2)
{
if (*p1 == '\0')
{
return 0;
}
p1++;
p2++;
}
return *p1 - *p2;
}
int main()
{
char* p1 = "abcdef";
char* p2 = "abqwt";
if (my_strcmp(p1, p2) > 0)
{
printf("%s > %s\n", p1, p2);
}
else if (my_strcmp(p1, p2) == 0)
{
printf("%s = %s\n", p1, p2);
}
else
{
printf("%s < %s\n", p1, p2);
}
return 0;
}
strncpy strncpy与strcpy相比较多了一个字母n,这个n代表的是需要拷贝字符的个数,也就是说strncpy需要关注拷贝字符的个数,而不是像strcpy那样关注’\0’。 char* strncpy(char* destination,const char* source,size_t num); 头文件:string.h 函数名:strncpy 函数参数: 【参数1】destination,类型:char*,拷贝字符的目的地位置,即接收字符的起始位置 【参数2】source,类型:char* ,拷贝字符的源地址,即拷贝字符串的开始位置。 【参数3】num,类型size_t,拷贝字符的个数,用来控制拷贝字符的长度。 函数返回类型:char* ,返回接收字符的起始位置。 函数功能:指定个数的字符串拷贝
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t count)
{
assert(dest != NULL);
assert(src != NULL);
char* start = dest;
while (count && (*dest++ = *src++) != '\0')
{
count--;
}
if (count)
{
while (count--)
{
*dest++ = '\0';
}
}
return start;
}
int main()
{
char arr1[10] = "abcdefg";
char arr2[] = "1234";
size_t len = 0;
scanf("%d", &len);
my_strncpy(arr1, arr2, len);
printf("%s", arr1);
return 0;
}
strncat strcat函数是字符串追加,在使用的时候以src的’\0’作为追加结束标志,因此在使用strcat来追加一个字符串数组本身的时候,会因\0被提前覆盖而无法追加成功。 strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\0’。(这也是我们在追加的时候,不用关注原dest, src中’\0’,仅需关注追加字符的个数的原因) char* strncat(char* destination,const char* source,size_t num); 头文件:string.h 函数名:strncat 函数参数: 【参数1】destination,类型:char*,被追加字符的目的地位置,即接收追加字符的起始位置 【参数2】source,类型:char* ,追加字符的源地址,即追加字符串的开始位置。 【参数3】num,类型size_t,追加字符的个数,用来控制追加字符的长度。 函数返回类型:char* ,返回接收字符的起始位置。 函数功能:指定个数的字符串追加 具体算法图解: 
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* src, size_t count)
{
assert(dest != NULL && src != NULL);
char* start = dest;
while (*dest++)
;
dest--;
while (count--)
if ((*dest++ = *src++) == '\0')
return start;
*dest = '\0';
return start;
}
int main()
{
char arr1[15] = "12345\0xxxxxxx";
char arr2[] = "abcd";
size_t count = 0;
scanf("%d", &count);
my_strncat(arr1, arr2, count);
printf("%s", arr1);
return 0;
}
strncmp strcmp用来比较两个字符串的大小,其算法思想如下:
 int strncmp(const char* p1,const char* p2,size_t count); 头文件:string.h 函数名:strcmp 函数参数: 【参数1】p1,char* 类型,表示用来比较的其中一个字符串。 【参数2】p2,char* 类型,表示用来比较的另一个字符串。 【参数3】count,size_t类型,表示参与比较的字符个数。 函数返回类型:int类型,根据比较的具体结果返回相应的数值 strstr 查找字符串,找子字符串。 char* strstr(const char* str1, const char* str2); 头文件:string.h 函数名:strstr 函数参数: 【参数1】str1,char* ,用于查找字符串的母串。 【参数2】str2,char*,待查找字符串的子串。 函数返回类型:char* ,返回查找到的地址 函数功能:查找字符串 / 找子字符串
四.结构体,枚举,联合
4.1结构体
结构体声明
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。结构体是一些值的集合,结构体的每个成员可以是不同类型的。 结构的声明:
struct tag
{
member_list;
}variable_list;
结构体关键字:struct 结构体的标签:tag 结构体的类型:struct tag 结构的成员列表:member_list 结构体变量列表:variable_list 例如描述一个学生:
struct Student
{
char name[20];
char id[20];
int age;
char sex[5];
};
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}*p;
p = &x 能在编译器中正常运行吗? 编译器会报出警告:编译器会把上面两个声明当成完全不同的两个类型,所以是非法的。 typedef-- - 类型重定义 能否用typedef来重定义匿名结构体类型呢?
typedef struct
{
int data;
Node* next;
}Node;
这样写是编译不过的,不可以用typedef来重定义匿名结构体类型,具体解决办法如下:
typedef struct Node
{
int data;
struct Node* next;
}Node;
struct Node* next; 不可以用Node* next; 来替代。因为typedef对结构体类型重定义,前提是结构体类型先完成创建后,再对其类型名称重定义一个新的类型名称,如果说在创建的结构体内部中就是使用重定义之后的类型名称?是不是还没创建完成就开始用了,这个地方可不能这样使用。
结构体自引用
在结构中包含一个类型为该结构本身的成员可以吗 ? 如果可以,那sizeof(struct Node)是多少 ? 结构体正确的自引用方式:
struct Node
{
int data;
struct Node* next;
};
这里面的结构体自应用方式并不是直接利用结构体来创建变量,而是创建指向该结构体类型的指针,我们知道,指针的大小跟其所指向的类型无关,仅跟平台环境有关。正因为指针大小的确定性,所以再自引用的时候结构体类型的整体大小也是可以确定的。
结构体变量的定义和初始化
如何定义结构体变量和初始化变量呢? 举例如下:
struct Point
{
int x;
int y;
}p1;
struct Point p2;
struct Point p3 = { 1,1 };
struct Point
{
int x;
int y;
}p1;
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10,{4,5},NULL };
struct Node n2 = { 20,{1,2},NULL };
4.2枚举
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
sun
};
enum sex
{
MALE,
FEMALE,
SECRET
};
enum color
{
RED,
GREEN,
BLUE
};
以上定义的enum Day, enum sex,enum color都是枚举类型。 枚举的优点 枚举的优点∶ 1.增加代码的可读性和可维护性 2.和#define定义的标识符比较枚举有类型检查,更加严谨。 3.防止了命名污染(封装) 4.便于调试 5.使用方便,一次可以定义多个常量
4.3联合
联合体也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(有的书籍也会将共用体称为联合体)。
#include<stdio.h>
union Un
{
char c;
int i;
};
int main()
{
union Un u;
printf("%d\n", sizeof(u));
printf("%d\n", sizeof(u.c));
printf("%d\n", sizeof(u.i));
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
return 0;
}
共用体的特点 共用体的成员是共用同一块内存空间的,这样一个共用体变量的大小,至少是最大成员的大小(因为共用体至少得有能力保存最大的那个成员)。 共用体大小的计算 1.共用体的大小至少是最大成员的大小。 2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
五.动态内存管理
5.1为什么存在动态内存分配
实际编程中,不仅需要大小固定的内存空间,往往还需要大小可变的内存空间。比如说如果要建立一个通讯录,用来存放相关信息(姓名、性别、年龄、电话等),这种复杂的数据,我们知道要用结构体类型来存储,而且要用结构体数组来存,但是问题是“这个数组的大小应该多大呢?”.如果长度给小了,那么就会导致数据溢出,进而引发程序崩溃。如果给大了,又会导致存储空间大量浪费,空间利用率低。 为了解决这一类问题,就出现了动态内存分配,内存空间按需索取,要多少给多少.
5.2动态内存函数介绍
通过系统提供的4个库函数实现,malloc\calloc\realloc\free malloc 函数原型:void * malloc(size_t size);size_t就是unsigned int(无符号整型) 这个函数的作用就是在动态存储区中分配一个长度为size个字节的连续空间,并返回指向该空间的指针。 1)如果开辟成功,则返回一个指向开辟好空间的指针。 2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 3)返回值的类型是void * ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。 4)如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。 动态开辟的空间如何释放和回收呢? C语言提供了一个专门完成这个功能的库函数— free free 函数原型:void free(void* p) free的作用就是释放指针变量p所指向的动态空间,使这部分空间能够重新被利用。 1)如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。 2)如果参数 ptr是NULL指针,则函数什么事都不做。 代码:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = (int*)malloc(10 * sizeof(int));
if (ptr == NULL)
{
perror("main");
return 0;
}
for (int i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", ptr[i]);
}
free(ptr);
ptr = NULL;
return 0;
}
5.3常见动态内存错误
1)对空指针NULL的解引用操作 2)对动态开辟空间的越界访问 3)对非动态开辟内存使用 free 释放 4)使用 free 释放一块动态开辟内存的一部分 5)对同一块动态内存的多次释放 6)动态开辟内存忘记释放(导致内存泄露)
5.4总结

六.C语言文件操作
6.1什么是文件
计算机中所说的文件是以计算机硬盘为载体,存储在计算机上的信息集合。例如我们熟悉的文本文件,图片文件,视频文件,程序等等。文件有各种各样的类型,但是在程序设计中,我们一般谈的文件有两种∶程序文件、数据文件 (1)程序文件 包括源程序文件(后缀为.c ), 目标文件(windows环境后缀为.obj), 可执行程序(windows环境后缀为.exe)。 (2)数据文件 文件的内容不是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。本章讨论的是数据文件。在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
6.2文件名
文件名包含3部分∶文件路径 + 文件名主干 + 文件后缀 例如︰c : \code\test.txt 文件路径:c : \code 文件名主干:test 文件后缀:.txt a.文件名主干的命名规则遵循标识符的命名规则。 b. 后缀用来表示文件的性质, 如:doc(Word生成的文件), txt(文本文件),dat(数据文件), c(C语言源程序文件)'cpp(C++源程序文件), for(FORTRAN语言源程序文件),pas(Pascal语言源程序文件),obj(目标文件), exe(可执行文件).ppt(电子幻灯文件), bmp(图形文件)等。 c.文件路径:分为相对路径和绝对路径 绝对路径:c : \code\test.txt 相对路径:test.txt
6.4文件缓冲区
ANSIC标准采用"缓冲文件系统"处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块"文件缓冲区"。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
6.5文件指针
缓冲文件系统中, 关键的概念是“文件类型指针”, 简称“文件指针”。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。 该结构体类型是由系统声明的,取名为FILE。 FILE * pf;//文件指针变量 定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
6.6文件的打开和关闭
fopen 函数原型:FILE* fopen(const char* filename, const char* mode); 函数功能:Open a file.(打开文件) 返回类型:Each of these functions returns a pointer to the open file.A null pointer value indicates an error.(如果打开成功,返回指向文件信息区的指针,如果返回失败,返回空指针NULL) 函数参数1:filename(文件名,实际上包括3部分内容,而不仅仅是文件名主干。如果文件路径未写,则默认本路径) 函数参数2:Type of access permitted(文件打开方式) fclose 函数原型:int fclose(FILE* stream); 函数功能:Closes a stream(fclose) or closes all open streams(_fcloseall).(关闭文件) 返回类型:fclose returns 0 if the stream is successfully closed._fcloseall returns the total number of streams closed.Both functions return EOF to indicate an error(关闭文件成功返回0,关闭文件失败返回EOF(值为 - 1)来报错) 函数参数:Pointer to FILE structure(文件指针)
6.7文件的随机读写
一般情况下, 在对字符文件进行顺序读写时, 文件位置标记指向文件开头, 这时如果对文件进行读的操作, 就读第1个字符, 然后文件位置标记向后移一个位置, 在下一次执行读的操作时, 就将位置标记指向的第⒉个字符读入。依此类推, 遇到文件尾结束。 可以根据读写的需要, 人为地移动文件位置标记的位置。文件位置标记可以向前移、向后移, 移到文件头或文件尾, 然后对该位置进行读写, 显然这就不是顺序读写了, 而是随机读写。 fseek函数 根据文件指针的位置和偏移量来定位文件指针。 函数原型:int fseek(FILE * stream,long int offset,int origin); fseek(文件类型指针, 偏移量, 起始点) ftell函数 返回文件指针相对于起始位置的偏移量 函数原型:long int fte11(FILE * stream); rewind 让文件指针的位置回到文件的起始位置 函数原型:void rewind(FILE * stream); 代码:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return;
}
int ch = fgetc(pf);
printf("%c\n", ch);
fseek(pf, -1, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
int ret = ftell(pf);
printf("%d\n", ret);
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
return 0;
}
6.8文件结束的判定
1.文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets) 例如︰ fgetc判断是否为EOF. fgets判断返回值是否为NULL. 2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。 例如∶ fread判断返回值是否小于实际要读的个数。 fgetc函数在读取结束的时候,会返回EOF 正常读取的时候,返回的是字符的AscII码值 fgets函数在读取结束的时候,会返回NULL 正常读取的是时候,返回存放字符串的空间起始地址。 fread函数在读取的时候,返回的是实际读取到的完整元素的个数,如果发现读取到的完整元素的个数小于实际要读取(指定要读取)的个数,这就是最后一次读取了。
七.部分习题总结
1.字符串的结束标志是:C语言规定:以’\0’作为有效字符串的结尾标记 EOF一般用来作为检测文本文件的末尾
2.strlen是用来获取字符串的有效长度的,结尾标记’\0’不包含在内。 strlen获取的规则非常简单:从前往后一次检测,直到遇到’\0’是就终止检测。
3.’\n’ 转义字符,代表换行,’\060’ 转义字符,060八进制数据,十进制为48,因此’\48’表示的就是’0’,’\q’ 什么都不是,\b’ 转义字符,表示退格
4.define不是关键字,是编译器实现的,用来定义宏的预处理指令,不是C语言中的内容。int、struct和continue都是C语言中包含的关键字。
5.指针是一种复合数据类型,指针变量内容是一个地址,因此一个指针可以表示该系统的整个地址集合, 故按照32位编译代码,指针占4个字节,按照64位编译代码,指针占8个字节(注意:不是64位系统一定占8个字 节,关键是要按照64位方式编译)
6.static的特性 static修饰变量 a. 函数中局部变量: 声明周期延长:该变量不随函数结束而结束 初始化:只在第一次调用该函数时进行初始化 记忆性:后序调用时,该变量使用前一次函数调用完成之后保存的值 存储位置:不会存储在栈上,放在数据段 b. 全局变量 改变该变量的链接属性,让该变量具有文件作用域,即只能在当前文件中使用 c. 修饰变量时,没有被初始化时会被自动初始化为0 static修饰函数 改变该函数的链接属性,让该函数具有文件作用域,即只能在当前文件中使用
-
switch语句中表达式的类型只能是:整形和枚举类型 -
下面程序的结果是: #include <stdio.h>#include <string.h>int main() { printf("%d\n", strlen(“c:\test\121”)) return 0; } strlen:获取字符串的有效长度,不包括’\0’,“c:\test\121”: 在该字符串中,\t是转移字符,水平制表,跳到下一个tab的位置;而\121表示一个字符,是将121看做8进制数组,转换为10进制后的81,作业为ASCII码值的字符,即:字符’Q’ ,故上述字符串实际为:“c: esty”,只有7个有效字符。
9.计算1/1-1/2+1/3-1/4+1/5 …… + 1/99 - 1/100 的值,打印出结果
#include <stdio.h>
int main()
{
int i = 0;
double sum = 0.0;
int flag = 1;
for(i=1; i<=100; i++)
{
sum += flag*1.0/i;
flag = -flag;
}
printf("%lf\n", sum);
return 0;
}
10.打印乘法口诀表,口诀表的行数和列数自己指定,如:输入9,输出99口诀表,输出12,输出1212的乘法口诀表。 /* 思路:
- 设计函数原型,不需要返回值,参数N表示乘法口诀表总共有多少行
- 设定两个循环,外层循环控制总共有多少行
内层循环控制每行有多少个表达式以及表达式中的内容 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int mulT(int n) {
int i, j;
for (i = 1; i <= n; i++) {
for (j = 1; j <= i; j++) {
printf("%d * %d = %d ", j, i, i * j);
}
printf("\n");
}
}
int main() {
int n;
printf("请输入需要打印的乘法表数:");
scanf("%d", &n);
mulT(n);
system("pause");
return 0;
}
11.猜数字游戏
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include<string.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
printf("********************************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("********************************\n");
}
void game()
{
int ret = rand() % 100 + 1;
int num = 0;
while (1)
{
printf("请猜数字:>");
scanf("%d", &num);
if (num == ret)
{
printf("恭喜你,猜对了\n");
break;
}
else if (num > ret)
{
printf("猜大了\n");
}
else
{
printf("猜小了\n");
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
}
while (input);
return 0;
}
srand函数: srand函数是随机数发生器的初始化函数。 原型:void?srand(unsigned int seed);srand和rand()配合使用产生伪随机数序列。 功能说明:srand设置产生一系列伪随机数发生器的起始点,要想把发生器重新初始化,可用1作seed值。任何其它的值都把发生器匿成一个随机的起始点。rand检索生成的伪随机数。在任何调用srand之前调用rand与以1作为seed调用srand产生相同的序列。 [1] 包含文件:stdlib. h 返回值:无 相关函数:rand,random ,randomize 12.编写代码在一个整形有序数组中查找具体的某个数 要求:找到了就打印数字所在的下标,找不到则输出:找不到。 /* 二分查找: 在一个有序的序列中,找某个数据是否在该集合中,如果在打印该数据在集合中的下标,否则打印找不到
具体找的方式:
- 找到数组的中间位置
- 检测中间位置的数据是否与要查找的数据key相等
a: 相等,找到,打印下标,跳出循环 b: key < arr[mid], 则key可能在arr[mid]的左半侧,继续到左半侧进行二分查找 c: key > arr[mid], 则key可能在arr[mid]的右半侧,继续到右半侧进行二分查找,如果找到返回下标,否则继续,直到区间中没有元素时,说明key不在集合中,打印找不到 易错点: - right的右半侧区间取值,该值决定了后序的写法
- while循环的条件是否有等号
- 求中间位置的方法,直接相加除2容易造成溢出
- 更改left和right的边界时,不确定是否要+1和-1
*/
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int key = 3;
int left = 0;
int right = sizeof(arr)/sizeof(arr[0])-1;
while(left<=right)
{
int mid = left+(right-left)/2;
if(arr[mid]>key)
{
right = mid-1;
}
else if(arr[mid]<key)
{
left = mid+1;
}
else
{
printf("找到了,下标是:%d\n", mid);
break;
}
}
if(left>right)
printf("找不到\n");
return 0;
}
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int key = 3;
int left = 0;
int right = sizeof(arr)/sizeof(arr[0]);
while(left<right)
{
int mid = left+(right-left)/2;
if(arr[mid]>key)
{
right = mid;
}
else if(arr[mid]<key)
{
left = mid+1;
}
else
{
printf("找到了,下标是:%d\n", mid);
break;
}
}
if(left>=right)
printf("找不到\n");
return 0;
};
13.关于实参和形参描述 函数调用如果采用传值调用,改变形参不影响实参,因为形参和实参是两个不同的变量,传参时不论是按照值还是指针方式传递,形参拿到的都是实参的一份拷贝,函数没有调用时,新参没有空间,函数的实参可能是变量,也可能是常量,也可能是宏,也可能是指针等等 **传值:**形参是实参的一份拷贝,函数运行起来后,形参是形参,实参是实参,形参和实参没有任何关联性,改变形参时,不会对实参造成任何影响。 **传地址:**形参是实参地址的一份拷贝,形参指向的实体是实参,对形参解引用后,拿到的内容就是实参,因此对形参解引用之后的内容进行修改,改变的就是实参
14.实现一个对整形数组的冒泡排序 /* 思路: 遍历数组,对数组中相邻的两个元素进行比较,如果需要升序,前一个数据大于后一个数据时,交换两个位置上的数据,直到所有的数据比较完,此时,最大的数据已经放在数组的末尾。 除最大数据已经排好序外,其余数据还是无需,对剩余数据采用与上述类似的方式进行处理即可 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include<stdio.h>
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void bubble(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] < arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
bubble(arr, sz);
print_arr(arr, sz);
}
15.创建一个整形数组,完成对数组的操作 a实现函数init() 初始化数组为全0 b实现print() 打印数组的每个元素 c实现reverse() 函数完成数组元素的逆置。
#include <stdio.h>
void init(int a[], int sz)
{
int i;
for (i = 0; i < sz; i++)
{
a[i] = 0;
}
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void reverse(int arr[], int sz)
{
int t;
int left = 0;
int right = sz - 1;
while (left < right)
{
t = arr[left];
arr[left] = arr[right];
arr[right] = t;
left++;
right--;
}
}
int main() {
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
reverse(arr, sz);
print(arr, sz);
return 0;
}
17.编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同? 输入例子: 1999 2299 输出例子:7 /* 复习逻辑操作: & 是按位与操作符,&& 是逻辑与,不是按位与,|| 是逻辑或 ,! 是逻辑反操作符,^按位异或 思路:
- 先将m和n进行按位异或,此时m和n相同的二进制比特位清零,不同的二进制比特位为1
- 统计异或完成后结果的二进制比特位中有多少个1即可
*/
#include <stdio.h>
#include <windows.h>
int main()
{
int m = 0;
int n = 0;
int q = 0;
int i = 0;
int count = 0;
printf("请输入两个数字:");
scanf("%d%d", &m, &n);
q = m^n;
for (i = 0; i<32; i++)
{
if ((q >> i) & 1 == 1)
{
count++;
}
}
printf("count=%d", count);
system("pause");
return 0;
}
18.指针的基本概念 指针变量中存储的是一个地址,指向同类型的一块内存空间 局部指针变量不初始化就是野指针 指针变量中存的有效地址可以唯一指向内存中的一块区域 野指针指向的空间时非法的,或者说该指针指向的空间已经不存在了,因此野指针不能使用 指向结构体类型变量的指针访问结构体中成员时,使用->操作符 二级指针是指针,不能说起比一级指针大,只能说二级指针指向的空间中存储的也是一个地址 指针中存储的是地址,地址可以看成一个数据,因此是可以比较大小的 两个指针相减,指针必须指向一段连续空间,减完之后的结构代表两个指针之间相差元素的个数 野指针是指向未分配或者已经释放的内存地址
19.输入一个整数数组,实现一个函数, 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分, 所有偶数位于数组的后半部分。 /* 思路:
- 给定两个下标left和right,left放在数组的起始位置,right放在数组中最后一个元素的位置
- 循环进行一下操作
a. 如果left和right表示的区间[left, right]有效,进行b,否则结束循环 b. left从前往后找,找到一个偶数后停止 c. right从后往前找,找到一个奇数后停止 d. 如果left和right都找到了对应的数据,则交换,继续a, */
#include<stdio.h>
#include<stdlib.h>
void adjust(int arr[], int len) {
int swp = 0;
int left = 0;
int right = len - 1;
while (left < right) {
while (arr[left] % 2 == 1) {
left++;
}
while (arr[right] % 2 == 0) {
right--;
}
if (left < right) {
swp = arr[left];
arr[left] = arr[right];
arr[right] = swp;
left++;
right--;
}
}
}
int main() {
int arr[] = { 9, 14, 25, 33, 56, 74, 13, 69, 45, 73, 81 };
int len = sizeof(arr) / sizeof(arr[0]);
adjust(arr, len);
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
system("pause");
return 0;
}
20.回调函数 回调函数就是一个通过函数指针调用的函数 回调函数一般通过函数指针实现 回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用。 21.模仿qsort的功能实现一个通用的冒泡排序 qsort函数简介: qsort函数包含在<stdlib.h>中 qsort函数声明如下: void qsort(void * base,size_t nmemb,size_t size ,int(*compar)(const void *,const void *)); 参数说明: base:要排序的数组 nmemb:数组中元素的数目 size:每个数组元素占用的内存空间,可使用sizeof函数获得 compar:指向函数的指针也即函数指针。 这个函数用来比较两个数组元素,第一个参数大于,等于,小于第二个参数时, 分别显示正值,零,负值。 代码:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int comp(const void* a, const void* b){
return *(int*)a - *(int*)b;
}
void Swap(int*a, int* b){
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
void Bubblesort(int* arr, int size, int width, int(*comp)(const void* a, const void* b)){
for (int bound = 0; bound < size; bound++){
for (int cur = size - 1; cur>bound; cur--){
int ret = comp(&arr[cur - 1], &arr[cur]);
if (ret>0){
Swap(&arr[cur - 1], &arr[cur]);
}
}
}
}
int main(){
int arr[] = { 9, 5, 2, 7 };
int size = sizeof(arr) / sizeof(arr[0]);
Bubblesort(arr, size, sizeof(arr[0]), comp);
for (int i = 0; i < size; i++){
printf("%d ", arr[i]);
}
system("pause");
return 0;
};
八.参考博客
https://blog.csdn.net/QIYICat/article/details/117896160?spm=1001.2014.3001.5501 https://blog.csdn.net/QIYICat/article/details/119346988?spm=1001.2014.3001.5501
|