一.内存管理:
内存四区:
1.一个C语言变量的作用域可以是代码块 作用域,函数作用域或者文件作用域。
代码块是{}之间的一段代码
2.register寄存器变量
通常变量在内存当中,如果能把变量放到CPU的寄存器里面,代码执行效率会更高
register int I
3.全局函数和静态函数
在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,静态函数只能在当前函数内使用
4.代码区
代码区code,程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的。
5.静态区
所有的全局变量以及程序中的静态变量都存储到静态区,比较如下两段代码的区别
int a =0; int main() { staticint b =0; printf("%p, %p\n", &a, &b); return0; } |
int a =0; staticint b =0; int main() { printf("%p, %p\n", &a, &b); return0; } |
6.栈区
栈stack是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。
7.堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。
堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。
8.堆的分配和释放:
malloc:void *malloc(size_t _size); malloc函数在堆中分配参数_size指定大小的内存,函数返回void *指针
free:void free(void *p); free负责在堆中释放malloc分配的内存,参数p为malloc返回的堆中的内存地址
9.malloc、free用法示例:三种结果相同,区别指针用法
第一种:
void getp(int *p){ printf("%x\n", p); //31fa48 printf("%x\n", &p); //31f974 printf("%x\n", *p); //0 printf("%x\n", *(&p)); //31fa48 printf("%x\n", **(&p)); //0 *p = malloc(sizeof(int) * 10); } int main() { int *p = NULL; printf("%x\n", &p); //31fa48 getp(&p); p[0] = 1; printf("%d\n", p[0]); free(p); return 0; }
第二种:
void getp(int **p){ printf("%x\n", p); //31fa48 printf("%x\n", &p); //31f974 printf("%x\n", *p); //0 printf("%x\n", *(&p)); //31fa48 printf("%x\n", **(&p)); //0 *p = malloc(sizeof(int) * 10); } int main() { int *p = NULL; printf("%x\n", &p); //31fa48 getp(&p); p[0] = 1; printf("%d\n", p[0]); free(p); return 0; }
第三种:
void getp(int *p){ printf("%x\n", p); //31fa48 printf("%x\n", &p); //31f974 printf("%x\n", *p); //0 printf("%x\n", *(&p)); //31fa48 printf("%x\n", **(&p)); //0 **(&p) = malloc(sizeof(int) * 10); } int main() { int *p = NULL; printf("%x\n", &p); //31fa48 getp(&p); p[0] = 1; printf("%d\n", p[0]); free(p); return 0; }
第四种:
int *getp() { return malloc(100); } int main(){ int *p = NULL; p = getp(); free(p); return 0; }
10.操作系统在管理内存的时候,最小单位不是字节,而是内存页
11.malloc申请的内存是随机的,不连续的,如果想要申请连续内存空间,可以用calloc,calloc用法跟malloc相同,如果malloc或calloc申请的内存空间不够用,想要申请更多空间,可以用realloc,realloc会在原有内存的基础之上,在堆中间增加连续的内存空间,如果原有内存没有连续空间可扩展,就会新分配一个空间,将原有内存copy到新内存,然后释放原有内存
malloc跟realloc分配的内存都不是连续的;
memset:把malloc或realloc申请的不连续的内存空间变成连续的
写法:
char *p=malloc(10);
char *p1=calloc(10,sizeof(char));
char *p2=realloc(p1, 20); //在原有内存p1基础上,在堆中增加内存
char *p3=realloc(NULL , 5); //等同于malloc(5)
memset(p , 0 ,10); //将不连续的p内存空间变成连续的,从第0个到第10个
12.static int f=0;用static声明的变量会在整个进程运行期间一直有效,是在静态区,但只能在当前函数内使用
13.如果一个函数要修改另一个函数的静态变量的值,可以通过调用函数的方式将这个静态变量返回然后对其修改
14.堆内存的使用情况:
情况一:int set[1000]; 此时set数组在栈中,超过了栈的存储空间,,可以用malloc在堆中创建超大数组。int *set=malloc(sizeof(int)* 100000);
情况二:int i=0; scanf("%d",&i); int array[i]; 这种方式创建动态数组会报错,,应该用malloc创建动态数组。int *array=malloc(sizeof(int)*i);
二.结构体:
结构体的定义与使用
structman { char name[100]; int age; }; struct man m = { "tom", 12 }; struct man m= { .name = "tom", .age = 12 };
1.结构体在内存中是个矩形,而不是一个不规则形状
2.结构体在内存的大小取决于结构体中的内容,同样是int4个字节,char1个字节,但是结构体是4的倍数大小,例:A的大小为8个字节,B的大小为12个字节,c也12个字节
struct A{ int a; char b; char c; } struct B{ int a; int b; char c; }
struct C{ char a; int b; char c; }
结构体内存大小是以最大长度的内容对齐的,例:D结构体最大长度字节为long long 8个字节,所以都以8对齐,上面的ABC以int(4个字节)对齐,D的大小为24个字节
struct D{ char a; short b; char c; int e; long long d; }
写成这种方式16个字节:因为short是以2对齐的
struct D { char c; char a; short b; int e; long long d; }
3.定义一个结构体的时候可以指定具体元素的位长
struct test{ char a : 2;//指定元素为2位长,不是2个字节长 };
4.结构数组:structman
m[10] = { {"tom",12
}, {"marry",10
}, {"jack",9
} };
5.一个结构体的成员还可以是另一个结构体
struct names{ char first[100]; char last[100]; }; struct man{ struct names name; int age; }; struct man m = { { "wang", "wu" }, 20 };
6.结构体变量之间可以互相赋值(区别数组)
struct str{
char s[100]; }; int main(){ struct str s1, s2; strcpy(s1.s, "hello");//把hello赋给s1.s //s2 = s1;<span style="white-space:pre"> </span>//把s1赋给s2和下面一行代码相同 //memcpy(&s2, &s1, sizeof(s1)); printf("%s\n", s2.s); return 0; }
7.指向结构体的指针:定义方式
struct str a; struct str *p = &a; (*p).a = 10; (*p).b= 20; 上面的定义方式等同于下面 p->a = 10; p->b = 20;
指针使用示例:
struct A{ int a; int b; }; struct A array[10] = { 0 }; p = array; p->a = 1; p->b = 2; p++; p->a = 3; p->b = 4;
也可以在堆中申请内存创建数组:
struct A{ int a; int b; }; p = malloc(sizeof(struct A) * 10); struct A *array = p; p->a = 1; p->b = 2; p++; p->a = 3; p->b = 4; free(array);
7.当结构体中有指针,不能直接给结构体内的指针多次赋值,多次赋值要申请内存
struct str{ char *name; int age; }; int main(){ struct str st = { NULL,0 }; st.age = 30; st.name = malloc(100); strcpy(st.name, "吴志刚"); printf("%d,%s\n", st.age, st.name); free(st.name); return 0; }
8.结构体作为参数:
struct st{ char name[1000]; int age; }; void print_student(struct student s){ printf("name=%s,age=%d\n", s.name, s.age); } void set_student(struct student s,const char *name,int age){ strcpy(s->name, name); s->age = age; } int main(){ struct student st = { "tom",20 }; set_student(&st, "mike", 100); print_student(st); //结果打印出mike 100 return 0; }
定义一个结构体作为参数的时候,尽量使用指针,而不是使用结构变量,这样代码效率高
9.联合体:
联合union是一个能在同一个存储空间存储不同类型数据的类型。
联合体所占的内存长度等于其最长成员的长度,也有叫做共用体。
联合体虽然可以有多个成员,但同一时间只能存放其中一种。
对于联合体来讲最基本的原则是,一次只操作一个成员变量,如果这个变量是指针,那么一定是处理完指针对应的内存之后再来使用其他成员。
unionvariant{ int ivalue; char cvalue; double dvalue; }; int main() { union variant var; var.cvalue = printf("%d\n", var.ivalue); printf("%p, %p, %p\n", &(var.cvalue), &(var.ivalue), &(var.dvalue)); return0; |
}
三.枚举类型
可以使用枚举(enumeratedtype)声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。
实际上,enum常量是int类型的。
枚举的本质就是int型的常量。
enumspectrum {red,yellow,green,blue, white, black }; enumspectrum color; color = if (color != |
1.默认值
默认时,枚举列表中的常量被指定为0,1,2等
enumspectrum {red,yellow,green,blue, white, black }; printf("%d, %d\n",red,black); |
指定值
可以指定枚举中具体元素的值
enumspectrum {red =10,yellow =20,green,blue, white, black }; printf("%d, %d\n",red,black); |
四.typedef:
typedef是一种高级数据特性,它能使某一类型创建自己的名字
typedef unsigned char BYTE |
1.与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值
2.typedef是编译器处理的,而不是预编译指令
3typedef比#define更灵活
直接看typedef好像没什么用处,使用BYTE定义一个unsigned char。使用typedef可以增加程序的可移植性。
4.通过typedef定义函数指针
typedefconstchar *(*SUBSTR)(constchar *,const char *); constchar *getsubstr(constchar *src,constchar { return strstr(src,str); } const |
const char *(*p[3])(constchar *,constchar *); |
在程序当中如果是定义一个可读的常量,适合用#define
如果定义的是一个具体的数据类型,那么typedef更加适合。
如果是定义一个函数指针,那么基本就typedef吧.
五.文件操作
1.不论操作什么类型的文件,第一步先打开一个文件,第二步,读写文件,第三步关闭文件。
2.fopen
r 以只读方式打开文件,该文件必须存在。
r+ 以可读写方式打开文件,该文件必须存在。用r+写文件时候,从文件开始位置写入
rb+ 读写打开一个二进制文件,允许读写数据,文件必须存在。
rw+ 读写打开一个文本文件,允许读和写。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
wb
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留),如果文件不存在,a的行为和w是一样的
a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
“b”只对windows有效,对于unix来讲是无效,
3.文件操作helloworld案例:
写入文件
int main(){ char s[1024] = { 0 }; FILE *p = fopen("D:\\temp\\a.txt", "w");//用写的方式打开一个文件 //w意思就是如果文件不存在,就建立一个,如果文件存在就覆盖 while(1){ memset(s, 0, sizeof(s)); gets(p); if (strcmp(s, "exit") == 0) break; int len = strlen(s); s[len] = '\n'; fputs(s, p); } fclose(p);//关闭这个文件 printf("end\n"); return 0; }</span>
读出文件:
int main() { char s[1024] = { 0 }; FILE *p = fopen("D:\\temp\\a.txt", "r");//用读的方式打开一个文件 while (!feof(p)) { //feof(p)如果已经到了文件结尾,函数返回真 memset(s, 0, sizeof(s)); fgets(s, sizeof(s), p); printf("%s", s); } fclose(p);//关闭这个文件 return 0; }</span>
4.二进制和文本模式的区别
——在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n" 。
——在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。
对于GBK编码的汉字,一个汉字两个字节,对于utf8来讲一个汉字3个字节,但如果英文字母都是一个字节
5.getc和putc函数
int main() { FILE *fp = fopen("a.txt", char c; while ((c = getc(fp)) !=EOF) { printf("%c", c); } fclose(fp); return0; } |
int main() { FILE *fp = fopen("a.txt", constchar *s ="hello world"; int i; for (i =0; i < strlen(s); i++) { putc(s[i], fp); } fclose(fp); return0; } |
6.EOF与feof函数文件结尾
程序怎么才能知道是否已经到达文件结尾了呢?EOF代表文件结尾
如果已经是文件尾,feof函数返回true。
7.fprintf,fscanf,fgets,fputs函数(都是操作文件的一行,不是单个字符)
这些函数都是通过FILE*来对文件进行读写。
——fprintf:功能和printf一样,fprintf是把数据输出到文件中,fprintf(p,"%s" ,buf); p是要输出的文件,buf是输出的值
——fscanf与scanf用法基本一致,fscanf是从一个文件读取输入,scanf是从键盘读取输入,用法:fscanf(p , "%s" ,buf);第一个参数是获取文件内容的指针,第二三个参数与scanf相同,fscanf不会读取行尾的’\n’,
——fgets会将行尾的’\n’读取到buf里面,用法:
int main() { FILE *p = fopen("D:\\temp\\a.txt", "r");//用读的方式打开一个文件 while (!feof(p)) {//feof(p)如果已经到了文件结尾,函数返回真 char s[1024] = { 0 }; memset(s, 0, sizeof(s)); fgets(s, sizeof(s), p); //通过p读取文件内容赋值给s,第二个参数控制读取数据多少 printf("%s", s); } fclose(p);//关闭这个文件 return 0; }
不论fprintf还是fputs都不会自动向行尾添加\n,需要代码中往buf的行尾写\n才可以达到换行的目录
8.stat函数
#include <sys/stat.h>
函数的第一个参数代表文件名,第二个参数是structstat结构。
得到文件的属性,包括文件建立时间,文件大小等信息。
9.fseek函数
函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。
实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,还是返回0,请小心使用。
第二个参数负数代表向前移动,整数代表向后移动。
第一个参数stream为文件指针
第二个参数offset为偏移量,单位:字节,正数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
fseek(fp, 3, SEEK_SET); |
10.ftell函数
函数ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
longlen=ftell(fp) |
11.fgetpos,fsetpos函数
fseek与ftell返回的是long类型,如果文件很大,超过long的范围,那么该函数会有问题,fgetpos与fsetpos函数可以处理更大的文件类型
返回值:成功返回0,否则返回非0
fpos_t ps = 0; fgetpos(fp, &ps); |
fpos_t ps = 2; fsetpos(fp, &ps); |
12.fflush函数
fflush函数可以将缓冲区中任何未写入的数据写入文件中。
修改配置文件,希望修改实时生效,那么每次修改完成之后我们fflush一次
13.fread和fwrite函数
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
注意:这 个函数以二进制形式对文件进行操作,不局限于文本文件
返回值:返回实际写入或读取的数据块数目
只要读取到文件最后,没有完整的读取一个数据块出来,fread就返回0
第一个参数代表void *,写入或者读取的缓冲区
第二个参数是代表写入或读取的时候最小单位的大小
第三个参数是代表写入或读取几个单位
第四个参数是FILE *
14.fread与feof
注意以下两段代码的区别
while (!feof(p)) { fread(&buf, } |
while (fread(&buf,1,sizeof(buf), p)) |
15.从一个文件读出数据,然后对其排序,又写入另一个文件:
#include <stdio.h> #include <stdlib.h> #include <string.h> void swap(int *a, int *b)//交换参数的值 { int tmp = *a; *a = *b; *b = tmp; } void pupple(int *p, int n)//冒泡排序 { int i; int j; for(i = 0; i< n; i++) { for(j = 1; j < n - i; j++) { if (p[j - 1] > p[j]) { swap(&p[j - 1], &p[j]); } } } } int main(void)//在堆建立一个数组对文件内容进行排序 { int index = 0;//这是个计数器 char buf[100]; FILE *p = fopen("D:\\temp\\a.txt", "r");//第一次打开a.txt目的是要知道这个文件有多少行 while(!feof(p))//如果没有到达文件结尾,那么循环继续,第一次循环的时候并不是要处理文件的内容,只是统计文件的行数 { memset(buf, 0, sizeof(buf));//每次读取文件一行之前都把这个buffer清空 fgets(buf, sizeof(buf), p);//从文件中读取一行 index++; } fclose(p); int *array = calloc(sizeof(int), index);//在堆中建立一个动态数组,动态数组的成员数量和a.txt文件的行一样多 p = fopen("D:\\temp\\a.txt", "r"); index = 0;//计数器从0重新开始 while(!feof(p))//如果没有到达文件结尾,那么循环继续 { memset(buf, 0, sizeof(buf));//每次读取文件一行之前都把这个buffer清空 fgets(buf, sizeof(buf), p);//从文件中读取一行 array[index] = atoi(buf);//将读取到的一行转化为int,赋值给数组成员 index++; } fclose(p); pupple(array, index);//将数组排序 p = fopen("D:\\temp\\b.txt", "w");//用写的方式打开b.txt int i; for(i = 0; i < index; i++) { memset(buf, 0, sizeof(buf));//每次操作之前都把buffer清空 sprintf(buf, "%d\n", array[i]);//将数组的成员转化为字符串 fputs(buf, p); } fclose(p); return 0; }