本人学习期间总结的所有C语言笔记:
1.运算符
一元运算符优先级大于二元运算符
,运算符优先级最低
[]大于*号运算符(用于指针数组时)
! > 算术运算符 > 关系运算符 > && > || > = > ,
2.字符型
char没有明确的定义为signed或unsigned,如果需要注意,要自己加上
'\'' '\"' '\\' '%%' '\?'
警报'\a' 回退'\b' 换页'\f' 换行'\n' 回车'\r' 水平tab'\t' 垂直tab'\v'
getchar();从stdin缓冲区中读取
getch();从键盘缓冲区中读取,在win下不回显,在Unix下回显 在<curses.h>中
字符测试函数:【包含在<ctype.h>中】
1) int isalnum(int c) //测试c是否是字母或数字,是的话返回为真,否则为假
2) int isdigit(int c) //测试c是否是数字
3) int isalpha(int c) //测试c是否是字母
4) int islower(int c) //测试c是否是小写字母
5) int isupper(int c) //测试c是否是大写字母
6) int tolower(int c) //转换为小写字母
7) int toupper(int c) //转换为大写字母
8) int iscntrl(int c) //测试c是否是控制字符
9) int isspace(int c) //测试c是否是空格
10)int isgraph(int c) //测试c是否是除了空格外的可打印的字符
11)int isprint(int c) //测试c是否是可打印字符
12)int ispunct(int c) //测试c是否是除了空格,数字,字母外的可打印字符,即是否为符号
3.字符串
字符串就是一个字符数组,结尾以'\0'作为标志
一个字符占用一个字节(汉字占用两个字节),从左到右依次存放,在字符串的结尾自动加入一个字节的结束标志'\0'
'a'与"a"的不同:'a'代表一个字符占用一个字节;"a"代表一个字符串占用两个字节.
字符串初始化:char str[10]="abc123!@#";//不能写成char str[10];str="dfh";(初始化声明的时候的=不代表赋值操作,只是初始化)
gets()
puts()
字符串转换函数:
1)int atoi(const char *str) //将字符串类型的数据转换为整型,参数为字符串首地址
2)double atof(const char *str) //将字符串类型的数据转换为双精度浮点数
字符串库函数:
1) strlen()
2) strcpy() strncpy()【复制指定字符个数的字符串】char *strncpy(char *s1, const char *s2, size_t n);
3) strcmp() int strncmp(const char *s1, const char *s2, size_t n);【比较前面n个字符】
4) strcat() 可以用在这种情况下:char ping[8]="ping ",ip[32]="www.baidu.com"; strcat(ping,ip); system(ping);
5) strtok() 字符串分割函数以s2为标准分割s1,char *strtok(char *s1, const char *s2);
6) bzero(void *s, size_t n) 字符串清理,参数1是要清空的字符串,参数2是要清空的位数
7) memset(void *s, int c, size_t n) 字符串填充,参数2为填充进去的内容,参数3为要填充的字节数
8) strchr(const char *s, int c) 在s中查找c第一次出现的地址
9) strrchr(const char *s, int c) 在s中查找c最后一次出现的地址
10)strstr(const char *s1, const char *s2) 在s1中查找s2第一次出现的地址
4.变量(Mac下)
int 4个字节 -2e15~2e15-1
float 4个字节 有效位数6~7位
double 8个字节 有效位数15~16位
char 1个字节 ASCII码字符,或-128~127
定义一个变量时,系统就自动的分配了空间,未赋值前,是随即放入的一个垃圾值
5.无符号、×××10进制、8进制、16进制
正常:u d o x
短: hu hd ho hx
长: lu ld lo lx
6.浮点数
默认情况下,浮点数是以双精度存储的,要明确使用的是单精度,要在数字后面加上字母f(如:57.0f),如果必须以long double格式存储,要在数 字的末尾加上字母l(如:57.0l)。
当读取double类型的数值时,在e、f、g前放置字母l,但是l只能在scanf中使用,不能在printf中使用,在printf中e、f、g就能表示double型
当读或写long double类型的值时,在e、f、g前放置字母L;
7.scanf函数和printf函数
scanf函数在读入数字时,会自动跳过空白字符,但在读入字符时,不会跳过空白字符
读写一个单独的字符时,可以用getshar和putchar代替:[ch=getchar(); putchar(ch)]
8.sizeof(类型名)
sizeof返回的值是无符号的整数
可以用sizeof(a)/sizeof(a[0])计算数组长度
9.类型定义(typedef)
typedef int Bool; //(后面变量名的首字母最好大写)
Bool flag; //类似与使用了int flag
C语言库自身使用typedef定义了一些不同的类型名,这些类型名通常以_t结尾,比如clock_t;
10.宏定义
【定义宏】#define BOOL int
数组和指针不能用宏定义,宏定义中的替换列表为空也是合法的,也可以包含对另一个宏的调用,但宏不能调用它本身.
一个数字常量,如果不是0和1,就最好定义成宏.
【带参数的宏】 #define 标示符(x1,x1,…,xn) 替换列表 //参数在替换列表中都要放在圆括号中
#运算符将一个宏的参数转换为字符串字面量;##运算符可以将两个记号(如标示符)粘在一起,成为一个记号.
#define CONCAT(x,y) x##y //CONCAT(a,b)会得到ab,但CONCAT(a,CONCAT(b,c))不会得到abc,而是aCONCAT(b,c)
【在宏中放入语句】放在do{ … }while(0)中,这时while后可以不加分号,在调用宏的时候再加分号;如果定义的时候就有分号,在调用的时候就不能再加分号了.
【取消宏】#undef 标示符
【预定义宏】_DATE_宏(mm dd yyyy)和_TIME_宏(hh:mm:ss)指明程序编译的时间,是字符串字面量.
_LINE_宏: 被编译的文件的行数,是整型常量 _FILE_宏: 被编译的文件的名字,是字符串字面量
_STDC_宏: 如果编译器接受标准C,值为1,是整型常量
11.数组
定义数组时,数组名不能和变量名相同,也不能和系统关键字一样
数组的地址就是数组元素的首地址,使用数组名a,代表的就是数组a[]的地址
多维数组:不常用,因为指针数组更加的灵活好用
无论是一维数组还是多维数组,通过单词const作为数组声明的开始可以把数组变为“常量”;
(如:const int months[]={31,28,31,30,31,30,31,31,30,31,30,31};)
12.函数
在调用函数前,都需要先声明函数,函数声明类似与函数定义的第一行,函数的声明必须与函数的定义一样。
【函数声明】 返回类型 函数名(形式参数); 【编译器只预处理函数,但是不分配内存】
【函数定义】 返回类型 函数名(形式参数) 【开辟内存空间,分配内存给函数,函数入口地址为函数名所在地址】
{
声明
语句
}
【函数调用】 函数名(实参);
函数无法返回数组;如果忽略返回类型,会假定为int型;
如果返回为空类型,必须写明void,如果没有形参,也要写明为void.如:void function(void)
数组做函数参数的时候,传递的是数组名,也就是地址
实参是通过值来传递的,在调用函数时,计算出实参的值然后传递(赋值)给对应的形参
函数分类:
1)从用户角度分有:
系统函数:库函数,不需要用户定义,包含头文件就可以直接使用的函数,printf();scanf();system();
自定义函数:用户根据自己的需要,声明函数的返回值类型,函数名,形参列表;但是必须符合函数声明的规则。可以是有参、无参、有返回值、无返回 值、还可以是函数的重写。
2)从返回值的角度:
有返回值:return,(函数有且只能返回一个返回值),可以是基本数据类型和指针等。
无返回值:void,在C89标准中可以忽略返回值类型,UNIX中默认为int,Window VC中为void;在C99标准中不可以忽略返回值类型。
3)从参数的角度:
带参数:需要告诉编译器参数的类型和数量
int main(int argc,char *argv[]);
int main(int argc,char **argv);
不带参数:参数列表为空,void,可以省略,即int main()或int main(void);就是不需要参数来参与运算。
13.返回语句
return:【return 表达式】如果return中表达式的类型和函数返回的类型不一致,系统会把表达式的类型转换成返回类型
exit:任何函数中都可以使用,【exit(表达式)】;包含在#include <stdlib.h>头文件中。
main函数的返回值是状态码,0表示正常结束,非0为异常结束。
14.递归
递归是函数嵌套调用的一种特殊形式,就是当一个函数调用另外一个函数的过程时,另一个函数恰好是它本身。
递归产生的条件:直接或间接的调用它本身,有一个出口(即结束递归的条件),要有一个明显的公理或公式,有规律可循。
1)直接递归调用
2)间接递归调用
注意:
1)递归调用在发生时,消耗了大量的系统资源,反复读写栈内存,CPU资源,会降低系统性能,不可控或深层递归都会造成系统不稳定
2)我们在开发过程中使用的都是浅层递归,如Windows搜索,使用了多线程,浅层递归(一般不超过3层)
3)递归调用也被用于分治算法
4)能不用就不用递归,如:.(){ .|. };. (.本身是一个函数,()为.函数的参数列表,;前为.函数的定义,最后一个.是调用这个函数)
15.迭代
迭代法又叫做递推,凡是能用递归来实现的迭代或递推都能实现。
八皇后问题,水仙花束问题,兔子繁殖问题,约瑟夫出圈问题,快速排序,汉诺塔
16.变量作用域
全局变量:
局部变量:
17.指针
简单的说,指针就是地址。
内存中的最小存储单元是字节,一个字节有8位,每一位都有自己的地址,指针就是他们的首地址。
基本数据类型的指针,就是他们在内存中的首地址。
构造类型的指针,数组,他的位置就是,数组名称所在内存的首地址,即a[0]的地址或者是数组名。
函数类型的指针,就是函数的入口地址,即函数名所在内存的首地址。
指针变量:和其他基本数据类型的变量类似,特殊的是他是能保存地址的变量。
【定义指针变量】类型标示符 *变量名(int *p);
指针变量初始化:
int *p=NULL;
int *q=&a;
int *s=100;
造成野指针的三种方式:
1.定义指针的时候没有初始化
2.指针指向一个局部变量,当局部变量执行完毕释放后,仍然指向该局部变量的时候(用完置NULL即可)
3.当指针指向一块动态的内存空间时malloc(new)使用完毕被free(delete)释放后,仍然指向该空间,(用完置NULL即可)
18.指针和常量的关系
1)常量指针:
指针指向了一个常量的指针变量,常量的内容不可以修改,但是地址可以修改。
int const *p; const在*号后,是p的值不能修改;const在*号前,是*p的内容不能修改
2)指针常量
是个常量,是指指针变量P的值不能被修改,p是个常量,而指针指向的对象的值是可以改变的。
int *const p;
19.指针和数组的关系
1)数组指针
指向一维数组的指针,即数组的首元素
int a[10];int *p=NULL;p=a;
是一个指向二维数组一行的指针变量,即二维数组的首行的地址
int (*p)[表达式];
2)指针数组
数组元素是指针变量
int *p[表达式];
20.指针和函数的关系
1)函数指针
就是指向函数名的指针变量
int (*p)(形参列表);
2)指针函数
它是一个返回指针类型的函数,即函数的返回类型是指针
int *p(形参列表);
如:int *adr(int x)
{
return &x;
}
这种情况,返回值还可以继续作为函数的参数来使用,如回调函数,比如Unix信号和信号量,signal(),malloc()
21.指向指针的指针
int a=0; int *p=&a; int **q=&p; //*p==a, **q==a
22.结构体(struct)
能够保存不同数据类型的一种构造数据类型
【关键字】struct
【定义结构体】 struct 结构体名{
成员变量1;
成员变量2;
…
成员变量n;
}; //分号表示结构体定义完毕
【结构体变量初始化】(结构体变量就是包含结构体所有属性的一个变量)
struct struct_name 变量名={ , , }; //struct struct_name 整体是变量的类型
或 struct struct_name{
成员变量1;
成员变量2;
}object_name={ , , };
【引用结构体成员变量】
. 或 ->(用于指针的时候)
【结构体和指针】
1)指向结构体的指针
struct struct_name *p; //p是指针变量名
p=&object_name; //&取地址符不能忘
【结构体的嵌套】
一个结构体中嵌套了另一个结构体
struct struct_name{
成员变量1;
成员变量2;
struct struct_name_a{
成员变量a;
成员变量b;
}object_name_a;
}object_name;
【引用】object_name.object_name_a.b;
【结构体变量(可以用指针)作为函数参数使用】
void struct_function(struct struct_name object_name_temp);
void struct_function(struct struct_name *p);
【结构体变量作为函数的返回值】
struct struct_name function_name()
{
return object_name;
}
【结构体数组】
数组中的每个元素都是一个结构体变量
struct struct_name{
成员变量1;
成员变量2;
}数组名[表达式]; //表达式可以为空,为空的时候是不定长度的数组
23.联合体(union)
用户自定义的数据类型,和结构体不同的是结构体变量独立使用各自成员的内存空间,联合体的所有成员共享存储空间,即使用联合体省内存.
定义联合体和结构体一样,只需要把struct改成union即可.
24.枚举(enum)
枚举的成员中只能用一种类型,成员内不指定序号的时候都以0开始,C语言把枚举变量和常量作为整数来处理
enum color{red, black, blue}a,b; //中间以逗号隔开
25.链表
链表是线性表的一种,是线性表的链式存储;是一种使用动态内存分配的,能保存不同数据类型的一种数据结构.构成链表的最小单位称为节点.
1)单链表
有一个头结点(非空)链表,结点之间有前驱和后继,尾结点无后继结点,指向空.节点有两部分组成数据域和指针域.头结点用于判断链表是否为空.
2)双链表
3)循环链表
26.位操作
1)位与&
用于置0
2)位或|
用于置1
3)位异或^
用于加密和解密:
0000 1100 0000 0110 (用第一次异或得到的值,再进行异或)
^ 0000 1010 ^ 0000 1010 (用同一个值作为异或的运算)
-------------- --------------
0000 0110 0000 1100 (重新得到了之前的值)
交换a,b的值: a=a^b; b=a^b; a=a^b;
4)位取反~
都是用补码来取反的
连带符号位一起取反,取反之后符号位为1的话(补码),减去1(反码),除去符号位其它位取反(原码)
5)左移<<
往左移,低位补0,左移一位相当于乘以2,左移n位相当于乘以2的n次方
6)右移>>
往右移,高位补符号位,右移一位相当于除以2,右移n位相当于除以2的n次方
Mac下左移右移符号位不变
27.内存管理
系统级内存管理(Unix系统提供的内存管理)
1)物理内存管理
2)虚拟内存管理
3)页面文件getpage
4)内存映射mmap
5)共享内存
brk();//在分配内存时效率很高,只有3个值:大于0(申请该大小的内存空间),等于0(完全释放内存),小于0(在当前的内存空间上释放该大小的空间)
sbrk();
OC中用的内存管理,计数,当计数为0的时候就自动释放(OC brk)
用户级内存管理
1)malloc
2)calloc
3)realloc
C语言执行过程中,在内存中的情况:
1)代码段(静态区域)
代码段由程序中的机器码组成,C语言中源代码编译后就形成了机器码,执行的时候,CPU的程序计数器指向了代码段的每一条代码,并由CPU依次执行
2)数据段(静态区域)
只读数据段,是程序使用一些不会被修改的数据,使用这些数的方式类似与查表式的操作,放置在只读存储器中;
已初始化读写数据段,是在程序中声明,有初值的变量,需占用寄存器空间,在程序执行时,位于可读写的内存区域内,供程序运行时读写
3)BSS段(未初始化读写数据段)(静态区域)
是在程序中声明,没有初始化的变量,这些变量在程序运行前不占存储器空间
4)堆空间(动态区域)
只在程序运行时出现,一般由程序员分配和释放,在具有操作系统的情况下,若程序员忘记释放,在程序结束后,系统会自动回收内存
5)栈内存(动态区域)
只在程序运行时出现,在函数内部使用的变量,函数参数及返回值,函数调用(递归)都将使用栈空间.栈空间是由编译器自动分配和释放的内存.
C语言提供的两种内存分配的方式:
1).静态内存分配(栈内存)
由编译器自动为我们分配的内存空间,一般栈内存的大小为10M~30M
内存的对齐:是指整型占4个字节,大于4个字节的数据类型,也按照4个字节来计算,而且是4的整数倍
内存的补齐:只在结构体中有,如果所占字节数不足4个数据类型,比如char,需将不足的字节数补空,补够4个字节
2).动态分配内存(malloc向堆申请内存)
malloc函数(分配后不初始化)
void *malloc(size_t size); //返回值是万能指针,在Mac下占8个字节,在Win下占4个字节
用于动态的向堆内存申请内存空间,使用完后,要使用free()函数释放该内存
一旦指针p指向动态分配的内存块,就可以忽略p是指针的事实,并且把它用作数组的名字,p[i].
free函数
calloc函数(分配之后初始化为0)(用于存储构造类型或复合类型数据)
void *calloc(size_t nmemb, size_t size); //有nmemb个元素,每个元素占size个字节,nmemb为1表示为任何的数据类型
realloc函数
void *realloc(void *ptr, size_t size); //ptr指向通过malloc或calloc或realloc分配的内存块
28.预处理
预处理器的行为是由指令控制的,这些指令是由#开头的一些命令.指令总是在第一个换行符处结束,若想在下一行继,在当前行末尾用\字符.
【宏定义】 #define指令定义一个宏, #undef指令删除一个宏定义.
【文件包含】#include导致一个指定文件的内容被包含到程序中.
1)#include <文件名> //用于C语言自身库的头文件
2)#include "文件名" //用于所有其他头文件,也包含任何自己编写的文件
3)#import <文件名> "文件名" //被包含的头文件只被编译一次,不会重复
【条件编译】#if, #ifdef, #ifndef, #elif, #else和#endif指令将一段文本块包含到程序中或排除在程序之外.
1)#if 常量表达式 //当预处理器遇到#if时,如果表达式的值为0,在#if和#endif之间的行会在预处理过程中从程序中删除,
语句 否则,在它们之间的行会被保留在程序中,并继续被编译器处理,这时的#if和#endif对程序无任何影响.
#endif 对于没有定义过的标示符,#if会把它当做是值为0的宏对待
2)defined 仅用于预处理器,当defined应用于标示符时,若标示符是个定义过的宏返回1,否则返回0.常用在#if指令中
#if defined(BULL)
...
#endif
3)#ifdef 标示符 //等价于 #if defined(标示符)
#ifndef
4)#elif 表达式1
#else
【特殊】#error, #line, #pragma更为特殊,较少用到.
1)#error 消息
2)#line n ("文件名") //line指令用于改变给程序行编号的方式,n在1和32767之间,之后的行编号为n+1,文件名可以没有
3)#pragma 记号 //对非常大的程序或需要使用指定编译器的特殊功能的程序很有用
29.链表
由一连串的结构(结点)组成的,其中每个结点都包含指向下一个链中结点的指针,最后的一个结点包含一个空指针.
for (p = first; p!=NULL; p = p->next)
30.文件
C程序中的流的访问是通过文件指针实现的,类型为FILE *fp;文件结束符EOF(由宏定义的);
1)打开文件
FILE *fopen(const char *filename, const char *mode); //filename可以包含路径信息,没打开返回空指针
打开的模式(mode):
"r":只读 "w":写,文件不需要存在 "a":追加,文件不需要存在
"r+":读和写,从文件头开始 "w+":读和写,文件存在就截取 "a+":读和写,文件存在就追加
如果打开的是二进制文件,需要在模式字符串中包含字母b, 即"rb"
2)关闭文件
int fclose(FILE *stream); //参数必须为文件指针,指针来自fopen或freopen,成功关闭文件返回0,否则返回EOF
3)为流附加文件
FILE *freopen(const char *filename, const char *mode, FILE *stream); //stream有stdin,stdout,stderr
4)格式化输入/输出
fscanf(fp,"%d", &i) //从fp中读入 fscanf(stdin, "%d", &i)等价于scanf("%d", &i)
fprintf(fp, "%d", i) //从fp中读出 fprintf(stdout, "%d", i)等价于printf("%d", i)
5)字符读写函数
fgetc(fp) getc(fp)
fputc(ch, fp) putc(ch, fp)
6)行的输入/输出
fgets(char *s, int n, FILE *stream) //n为限制要读入字符的数量, 为字符串的时候要额外的加1, 表示为'\0'
gets(char *s) //逐个读取字符
fputs(const char *s, FILE *stream) //系统不会自己写入换行符, 可以人为的加入"\r\n"
puts(const char *s) //系统会自动写入换行符
7)块的输入/输出(主要用于操作二进制文件)
fread()
fwrite()
8)fflush(fp) //为fp清洗缓冲区 fflush(NULL) //清洗全部缓冲区
remove("filename") //删除文件,参数是文件名,而不是文件指针
rename("filename", "newfilename") //重命名文件名, 参数也是文件名,不是文件指针, 文件此时要是关闭状态
fcopy f1.c f2.c //把文件f1.c复制给文件f2.c
feof(fp)
ferror(fp)
fseek //文件位置 文件开始: SEEK_SET 文件当前位置: SEEK_CUR 文件结尾: SEEK_END
31.gcc编译的过程
预处理(Pre-processing):-E
编译器将C源代码中包含的头文件如stdio.h编译进来(.i)
编译(Compliling):-S
编译器首先检查代码的规范性, 是否有语法错误等, 以确定代码实际要做的工作, 无误后, 将代码翻译成汇编语言(.s)
汇编(Assembling):-c
把编译阶段生成的.s文件转成二进制目标代码(.o)
链接(Link):
将输出文件.o链接成最终的可执行文件
32.函数库
静态库(.a): 是指在编译链接时, 把库文件中的代码全部加入到可执行文件中, 因此生成的文件比较大, 但在运行时就不再需要库文件了.
动态库(.so): 在编译链接时, 并没有把库文件的代码加入到可执行文件中, 而是在程序执行时由运行时链接文件加载库, 节省了系统开销.
gcc编译时, 默认使用动态库.