C语言基础知识总结

1.++和--

自增运算符和自减运算符:前缀和后缀两种情况

1)表达式++n先将n的值递增1,然后再使用变量n的值:如果n的值为5,x = n++; 执行后的结果,x的值置为5, n=6

2)表达式n++先使用变量n的值,然后再将n的值递增1:如果n的值为5,x = ++n; 执行后的结果,x的值置为6, n=6

在不需要使用任何具体值且仅需要递增变量的情况下,前缀方式和后缀方式效果相同。

2.return、exit区别

调用一个函数,此函数的入口地址会被保存起来,放在某个确定的寄存器中,函数结束前的return语句会找到函数地址,

然后在被调用函数结束后,执行其下一条语句。

exit使进程直接终止

3.结构体相关知识点 (重点)

1)聚合数据类型:能够同时存储超过一个的单独数据。C语言提供两种类型的聚合数据类型,数组和结构体。

结构体也是一些值的集合,这些值称为它的成员,一个结构体的各个成员可以有不同的类型。每个结构体有

自己的名字,它们通过名字访问结构体成员。

2)结构体声明与定义:

声明的一般形式:

a.struct student{int num;char sex[5];char addr[10];}; struct student s1,s2;

b.在声明类型的同时定义变量 struct 结构体名{ 成员列表 }变量名表列;

c.直接定义结构体类型变量: struct {成员表列}变量名表列;(即不出现结构体名) 注意:声明结构时,可以使用另一种良好的技巧,即用typedef 创建一种新的数据类型,如: typedef struct{int num;char name[10];char sex;}Person;

此时Person是个类型名而不是一个结构标签,所以后续的声明可以是:Person x; Person y[20],*z;

3)结构体成员的访问

struct COMPLEX{ float f;int a[20];long *lp;struct SIMPLE s;

struct SIMPLE sa[10];struct SIMPLE *sp; };

a)结构成员的直接访问: 通过点操作符(.),例如:struct COMPLEX comp;我们要访问comp这个结构体变量

中int a[20]这个成员,可以用:comp.a; 点操作符的结合性是 自左向右,如((comp.sa)[4]).f,可以直接comp.sa[4].f;

b)结构体成员的间接访问: 如果我们拥有一个指向结构体的指针,如 void func(struct COMPLEX *cp); 我们要访问这个

结构体的成员f,那么 (*cp).f;=====>>.操作符的优先级高于*,所以我们必须要加(),这个

操作有点麻烦,所以C语言定义了一个

更为方便的操作符:->(箭头操作符),cp->f就可以达到同样效果。

注意:->的左操作数必须是一个指向结构体的指针。

c)结构体的自引用

例1:struct SELF1{ 这个结构体的自引用是非法的,因为成员b是另一个 int a; 完整的结构,其内部还包含它自己的成员b,然后它的第二 struct SELF1 b; 个成员又是一个完整的结构b,这样一直重复下去。 };

例2:struct SELF1{ 这个是合法的,区别在于:b现在是一个指针而不是 int a; 结构。编译器在结构长度确定之前就已经知道了指针的长 struct SELF1 *b; 度,所以这种类型的自引用是合法的。

};

例3:typedef struct{ typedef struct SELF_REF1{

int a; int a;

SELF1 *b;(此处SELF1尚未定义) struct SELF_REF1 *b;//解决方案:定义一个结构标签来声明b

}SELF1; }SELF1;

这个是错误的。 这个是正确的。

4)结构体的初始化

结构体初始化和数组初始化很相似。一个位于一对{}花括号内部、由逗号(,)分割的初始值列表可用于结构体各个成员的初始化.

如果初始列表的值不够,剩余的结构成员将使用缺醒值进行初始化。

例如:struct INIT_EX{

int a;

short b[10];

Simple c;

}x = {10,{1,2,3,4,5},{25,'x',1.9}};

也可以使用一条简单的赋值语句将一个结构体变量的值赋给另一个相同类型的结构体变量。例如:如果book1和book2是同一类型结构体变量,

则book2 = book1;此语句有效。

5)结构体的内存分配

struct ALIGN{ struct ALIGN2{

char a; 1个字节 int b; 4个字节

int b; 4个字节 char a; 1个字节

char c; 1个字节 一共用去12个字节 char c; 1个字节 一共用去8个字节。

}; };

注意:我们在声明中对结构体列表重新排列,让那些边界要求最为严格的成员首先出现,对边界要求最弱的成员最后出现。

sizeof操作符能够得出一个结构体的整体长度,包括因边界对齐而跳过的那些字节。如果你必须确定结构某个成员的实际

位置,应该考虑边界对齐的因素,可以使用offsetof宏(stddef.h):

一般格式: offsetof(type,member) type 是结构的类型; member 是你需要的那个成员名;表达式的结果

是一个size_t 值,表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节。

offserof(struct ALIGN,b) 返回值为4.

6)结构体 作为函数参数的结构

4.realloc() calloc() malloc() memset()

动态内存分配:为什么使用动态内存分配?

当我们声明数组时,我们必须指定数组的下标,即数组的长度。但是有些时候我们无法提前知道数组的长度,它所需

要的内存空间取决于输入数据,所以此时,我们就可以使用动态内存分配来解决这个问题!

1)void realloc(void *ptr,size_t new_size);

realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,可以使一个内存扩大或缩小。如果它用于扩大一个

内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存的后面,新内存并未以任何方法进行初始化。如果缩小,

该内存尾部内存被拿掉,剩余部分内存是原先内容依然保留。

如果原先内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。

如果realloc函数的第一个参数为NULL,那么它的行为就和malloc一模一样。

2)malloc 和 free (都在头文件stdlib.h中声明)

void *malloc(size_t size);

size 为所需要的内存字节数,成功的话 malloc 返回一个指向被分配的内存块起始位置的指针。否则,返回一个NULL指针。

另外,malloc分配一块连续的内存,且实际分配的可能会比请求的多点,但不确定。 void free(void *pointer);

free参数要么是NULL,要么是malloc,calloc,realloc返回的值,为NULL时,不产生任何效果。

3)calloc

void *calloc(size_t nelem,size_t el_size);

calloc也用于分配内存,它与malloc的区别在于它在返回指向被分配内存的指针之前,先把这块内存初始化为0.

nelem表示所需元素的数量,el_size表示每个元素的字节数。

5.字符串相关操作

1)字符串基础

在C语言中没有专门的字符串变量,如果想将一个字符串放在变量中保存,必须使用字符数组,即用一个字符型数组来

存放一个字符串,数组中每个元素存放一个字符。

2)字符串常量

字符常量是由一对单撇号括起来的单个字符;字符串常量是由一对双撇号括起来的字符序列。

例如'a','c'是字符常量,"how do you do","china"是字符串常量。

C规定:在每一个字符串常量的结尾加一个“字符串结束标志”--?\0?,以便系统判断字符串是否结束。'\0'是

一个ASCII码为0的字符(空操作字符),它不引起任何控制动作,也不是一个可显示的字符。

如果有一个字符串常量“CHINA”,实际上在内存中是 C H I N A \0。它占内存单元为6个字符,而不是5个。

3)用于字符串比较的函数是strcmp,一般形式如下:

int strcmp(char const *s1,char const *s2);

如果s1大于s2,则返回一个大于0的数;如果相等,返回0;s1小于s2则返回一个小于0的数值。

4).用于字符串复制的函数是strcpy,一般形式如下:

char *strcpy(char *dst,char const *src); char *strncpy(char *dst,const char *src,size_t n);

作用是将src字符串复制到dst中,如果参数src和dst在内存中出现重叠,其结果是未定义的。由于dst参数将进行修改,所以

它必须是个字符数组或者是一个指向动态内存分配的数组的指针,而不能使用字符串常量。 返回值,返回它第一个参数dst的一份拷贝,就是一个指向目标字符串数组的指针。

6.define宏定义、typedef 的使用

编译一个C程序涉及到很多步骤,其中第一步被称作 预处理(preprocessing)阶段, C预处理器在源代码编译之前对其进行一些

文本性质的操作。主要任务有:删除注释、插入被#include 指令包含的文件的内容、 定义和替换有#define 指令定义的符号

1)define宏定义

#define指定简单用法:为数值命名一个符号。如#define PRICE 30 有了此指令以后,每当出现符号PRICE,预处理器就会把

它替换成30

使用#define 指令,我们可以把任何文本替换到程序中。例:#define do_forever for(;;) 使用更具描述性的符号来代替实现无限循环

#define CASE break;case 定义一种简单记发,使得switch语句看起来简化

技巧1 :使用预处理器把一个宏参数转换为一个字符串。#arguement 这种情况被处理器

翻译成为 arguement。

例如: #define PRINT(FORMAT,VALUE) \

printf("The value of" #VALUE \

"is" FORMAT "\n",VALUE)

...

PRINT("%d",x+3);

它产生的结果为:

The value of x+3 is 25

2)typedef

C语言支持typedef机制,它允许你为各种数据类型定义新名字。typedef声明写法:typedef char *ptr_to_char;

这个声明把标识符ptr_to_char作为指向字符的指针类型名字:ptr_to_char a; ==>char *a; 使用typedef声明类型可以减少使声明变得又臭又长的危险(例如unsigned long double *),尤其是复杂的声明。

注意:

我们用typedef而不是#define来创建新的类型名,因为#define 无法正确处理指针类型,例如:

#define d_ptr_to_char char *

d_ptr_to_char a,b; 它只正确的声明了a,而b却被声明成了一个char类型。

7.链接属性 作用域 文件类型

1).作用域(scope)

4种类型:文件作用域、函数作用域、代码块作用域、原型作用域

文件作用域(全局):任何在所有代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处一直到

它所在的源文件结尾处都是可以访问的。

例如右侧:a , b都具有文件作用域。另外在文件中定义的函数名也具有文件作用域,因为函数名本身不属于任何

代码块如int d(int e)。

代码块作用域(局部):位于一个花括号之间的所有语句称为一个代码块. 1.int a; 任何在代码块的开始位置声明的标识符都具有代码块作用域(block scope)。 2.int b(int c); 表示它们可以被这个代码块中的所有语句访问。 3.int d(int e)

例如:f1,g1,f2,g2,i都具有代码块作用域,另外函数定义的形式参数 4.{

如e也具有代码块作用域。 5. int f1,i;

当代码块处于嵌套状态时,如果内层代码块有标识符名字和外层标识符名字相同 6. int g1(int h);

则内层那个标识符将隐藏外层标识符,如i。 7. ...

8. {

原型作用域:只适用于在函数原型中声明的参数名,如2.int c,6.int h, 9. int f2,g2,i;

在函数声明中,函数参数名字并非必须。但是,如果出现参数名,可以随意取名字, }

不必与函数实际调用时实参匹配。 }

原型作用域防止这些参数名与程序其他部分名字冲突,事实上,唯一可能出现的冲突就是在同一个声明中不止一次地使用同一个名字。

函数作用域:只适用于语句标签,语句标签用于goto语句。可以简化为一条规则:一个函数中的所有语句标签必须唯一,最好不用这个知识。

2).链接属性:None,external,internal

None:没有链接属性标识符,总是被当作单独的个体

internal:内部链接属性标识符,在同一个源文件中所有声明指向同一个实体;如果位于不同源文件的多个声明,则分属不同的实体。

external:外部链接属性标识符,无论声明多少次,位于几个源文件中,都表示同一个实体。

全局变量、全局函数默认为external.要修改全局变量或函数的链接属性external到internal,就在变量和函数前加static.

另外:当extern 关键字用于源文件中一个标识符的第一次声明时,它指定该标识符具有external链接属性。但是,如果它用于

该标识符的第2次或以后的声明时,它并不会更改由第一次声明所指定的链接属性。如: static int i;

int func()

{

int j;

extern int k;

extern int i;此处的extern 并不能改变 i 的链接属性。

...

}

3)存储类型(storage class) 是指存储变量值的内存类型。

变量的存储类型决定变量何时创建、何时销毁以及它的值可以保存多久。 有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。

变量的缺醒存储类型取决于它的声明位置。

a) 普通内存 凡是在代码块之外声明的变量总是存储于静态内存中,称为静态变量。 对于此类变量,无法为它们指定其他的存储类型。 静态变量

在程序运行之前创建,在程序运行期间一直存在。

b) 自动变量 在代码块内部声明的变量的缺醒存储类型是自动的(automatic),也就是存储于堆栈中,称为自动变量。 当程序执行到声明自动

变量的代码块时,自动变量才被创建,当程序执行流离开该代码块时,这些自动变量便自行销毁。

对于在代码块内部声明的变量,如果给它加上static 关键字,可以使它的存储类型从自动改变为静态。注意:修改变量存储类型不等与修改该变量

的作用域。 函数的形参 不可以声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。

c) 关键字register 可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。

通常情况下,寄存器变量比存储在内存的变量访问起来效率更高。但是编译器不一定理财register关键字,如果有过多的变量被声明为register,它

只选取前几个实际存储于寄存器中,其余按普通自动变量处理。 典型情况下,你希望把使用频率最高的那些变量声明为寄存器变量。有些计算机中,把指针

声明为寄存器变量,程序的效率会得到提升,尤其是那些频繁执行间接访问操作的指针。 你可以把函数形参声明为寄存器变量,编译器会在函数的起始位置

生成指令,把这一些值从堆栈中复制到寄存器中。但是,完全有可能,这个优化措施节省的时间和空间的开销还抵不上复制这几个值所用的开销。

寄存器变量的创建和销毁时间和自动变量相同,但它还需要一些额外工作。 在一个使用寄存器变量的函数返回之前,这些寄存器变量先前存储的值必须

恢复,确保调用者的寄存器变量未被破坏。多数机器用堆栈完成这个功能。

d)初始化 在静态变量的初始化中,可以显式的指定初始值;如果不显式指定,则初始化为0;

自动变量初始化过程中,它没有缺醒的初始值,而显式的初始化将在代码块的起始处插入一条隐式的赋值语句。

这个技巧会造成4个后果:

声明变量的同时进行初始化 与 先声明后赋值 只有风格差异,无效率之别;

这条隐式的赋值语句使自动变量在程序执行到它们所声明的函数时,每次都重新初始化;

第三算是个优点, 由于初始化在运行时执行,所以我们可以用任何表达式作为初始值; 除非你对自动变量显式的初始化,否则当自动变量创建时,它们的值都是垃圾。

8.FILE fopen() fprintf() fgets(,,) fflush()...

1)FILE:

缓冲文件系统:所谓缓冲文件系统 是指 系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。

从内存向磁盘输出数据必须先送到内存的缓冲区,装满缓冲区后才一起送到磁盘去。ASICC标准只采用该系统。

非缓冲文件系统:指系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。

在缓冲文件系统中有一个关键的概念--文件指针,每个被使用的文件都在内存中开辟一个区,用来存放

文件的有关信息(如文件名字、文件状态、文件当前位置...),这些信息是保存在一个结构体变量中的,这个

结构体类型是由系统定义的,为FILE。

typedef struct{

short level; 缓冲区 满 或 空 的程度

unsigned flags; 文件状态标志

char fd; 文件描述符

unsigned char hold; 如无缓冲区不读取字符

short bsize; 缓冲区大小

unsigned char *buffer;数据缓冲区的位置

unsigned ar *curp; 指针,当前的指向

unsigned istemp; 临时文件,指示器

short token; 用于有效性检查

}FILE;

2)文件的打开(fopen函数):

FILE *fp; fp是一个指向FILE类型结构体的指针变量。可以使fp指向某一个文件的结构体变量,从而

fp=fopen(文件名,使用文件方式) 通过该结构体变量中的文件信息能够访问该文件。 例如:fp = fopen("a1","r"); 表示要打开名字为a1的文件,使用文件方式为“读入“,fopen函数带回指向a1文件的指针并赋给fp,

这样fp就和文件a1建立联系了。

3)文件的关闭(fclose函数):

使用完一个文件后应该关闭它,以防止它再被误用。“关闭“就是使文件指针变量不指向该文件,也就是文件指针变量与文件脱勾,

此后不能再通过此指针对原来与其相联系的文件进行读写操作。

一般形式:fclose(文件指针)。

例如: fclose(fp);

注意:

如果不关闭文件可能会丢失数据,因为,如前所述,向文件写数据时,数据先输出到缓冲区,待缓冲区满后才正式输出给文件。如果当

数据未充满缓冲区而程序结束运行,就会将缓冲区数据丢失。用fclose函数关闭文件,可以避免此问题,它会先把缓冲区数据输出到磁盘文件,

然后才释放文件指针。

4)fprintf函数和fscanf函数

fprintf函数和fscanf函数与printf,scanf函数作用相仿,都是格式化读写函数。只有一点不同:fprintf和fscanf函数的读写对象

不是终端而是磁盘文件。

一般调用方式:

fprintf(文件指针,格式字符串,输出表列);

fscanf(文件指针,格式字符串,输入表列);

例如:fprintf(fp,"%d,%6.2f",i,t);作用是将i、t按照%d %6.2f的格式输出到fp指向的文件上。

fscanf(fp,"%d,%f",&i,&t);磁盘文件上如果有以下字符,3,4.5,则将磁盘文件中的数据3送给变量i,4.5送给变量t。

5)fgets与fputs函数

fgets函数的作用是从指定文件读入一个字符串。

例如:fgets(str,n,fp); n为要求得到的字符个数,但只从fp指向的文件输入n-1个字符,然后在最后加一个?\0?字符,然后将其放入str中,

如果在读完n-1个字符前遇到换行符或EOF(-1),读入即结束。fgets函数返回值为str的首

地址。

fputs函数作用是向指定的文件输出一个字符串。

例如:fputs("china",fp); 把字符串“china”输出到fp指向的文件。fputs函数中第一个参数可以是字符串常量、字符数组名或字符型指针。

字符串末尾?\0?不输出。若输出成功,函数值为0;失败为EOF。

6)fflush函数

fflush函数,它迫使一个输出流的缓冲区内的数据进行物理写入,不管它是不是已经写满,原型如下:int fflush(FILE *stream);

fflush(stdin) 刷新标准输入缓冲区,把输入缓冲区里的东西丢弃

fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上

相关推荐