《C和指针》
Chap 01 快速上手
1.注释代码尽量使用//而不是/**/。因为如果/**/中也有其他的注释,就会出问题。或者可以使用如下方式注释代码:
#if 0 Statements #endif |
Chap 02 基本概念
1.编译、链接生成程序的过程:源代码.c经过编译器编译成目标代码.o(此时预处理器会执行预处理操作),然后多个目标代码和各种库文件.a/.so经过链接器链接成可执行程序。
2.程序的执行:首先加载程序到内存,这样非堆栈存储的变量(例如char* c=”abc”中c字符串就是非堆栈存储的变量)将进行初始化。然后申请运行时堆栈,存储临时变量和函数返回地址,同时也可以使用静态内存(变量的生命周期一直保持到程序退出)。最后程序退出,内存释放,各种堆栈信息清空。
Chap 03 数据
1.整数家族:[signed/unsigned] char/short int/int/long
2.使用typede而不使用宏定义类型,因为#define PC char*对于PC a,b;b的定义是char而不是char*。
3.Extern和static用于修饰链接属性。Extern声明全局变量,static用于声明文件内变量。
4.内存的构成:可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。
a.静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
b.栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
c.堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。
此外,代码区存放函数体的二进制代码。文字常量区用于存储常量字符串,程序结束后由系统释放。
PS:const常量默认为static存储,如果加上extern,则为外部存储类型。Static用于限定变量或者函数访问作用域在文件内并声明变量位于静态存储区,生命周期到程序结束。
Chap 04 语句
1.switch语句接受的表达式类型是整形。包括char、short int、int、long。
Chap 05 操作符和表达式
1.getch函数返回整形的原因是因为EOF存储的内容可能比char所占用的空间大。
2.整形运算总是至少以缺省类型精度(int)进行,因此char a = b+c;(b、c是char和short int)b/c会转换为整形int执行。
Chap 06 指针
1.计算机内存只存储0和1,最小单位是一个字节,由8个位组成,例如char。
2.不能对NULL指针解引用。
Chap 07 函数
1.函数的参数都是传值,数组因为数组名表示数组首地址指针,因此传递的时数组首地址值。
Chap 08 数组
1.数组访问时,使用指针访问的效率可能比使用下标访问效率高,也可能相等。下标的优势是调用清晰,可读性高。
(1)数组下标:
int array[10], a; for(a = 0; a < 10; a += 1) array[a] = 0; |
为了对下标表达式求值,编译器在程序中插入指令,取得a的值,并把它与整形的长度(也就是4)相乘。这个乘法需要花费一定的时间和空间。
(2)指针:
int array[10], *ap; for(ap = array; ap < array + 10; ap++) *ap = 0; |
ap++中,++其实就是在ap当前指针所指的位置处,加上一个ap所指元素的类型的长度,这里就是int,即4。因此程序执行期间没有乘法操作,效率高。
而对于如下语句:
a = getValue(); *(array + a) = 0; |
a的值需要在程序运行时确定,需要a的值与sizeof(int)相乘,效率与使用下标相同。
2.指针数组:int *a[3];a中存储的是int*类型的指针;
3.指向数组的指针:int (*a)[3];a是一个指向int[3]数组的指针。区分的方法类似于函数指针,记住[]的优先级高于*。
Chap 09 字符串、字符和字节
1.字符串函数要使用带有长度限制的带n版本。
2.常用字符串函数:strlen/strncmp/strncat/strncpy/strchr(指向第一次字符出现的位置)/strrchr(返回最后一个字符出现的位置)/strpbrk(返回目标字符串中任一字符在源字符串中第一次出现的位置)/strstr(查找目标字符串在源字符串中出现的位置)/strspn(源字符串中连续出现目标字符中任意字符的个数)/strcspn(字符串中连续出现非目标字符中任意字符的个数)/strok(字符串分割)/strerr(接受errno并返回一个对其的字符描述)
3.字符分类函数:iscntrl、isspace、isdigit、isxdigit、islower、issupper、isalpha、isalnum、ispunct、isgtaph、isprint
4.字符转换函数:tolower、toupper
5.内存操作:下面的函数可以处理任意字节,头文件是string.h
a.memcpy内存复制:memcpy(dst,src,n*sizeo(srctype)),要求dst和src地址不能重叠,否则结果未定义。
b.memmove内存移动,类似于memcpy,但是源、目的可以重叠。
c.memset内存设值:memset(buffer,ch,SIZE)强buffer开始的SIZE个字节替换为字符ch。
d.memcmp:内存值逐字节比较。
e.memchr:从字符串中查找字符并返回位置,查不到返回NULL。
Chap 10 结构和联合
1.结构体初始化时可以使用{}进行。
2.包含头文件stddef.h可以使用offsetof计算结构体中变量距离结构体开始位置的偏移。
Offsetof的定义:
#define offsetof(s,m) (size_t)&(((s *)0)->m) |
3.GNU提供__attribute__((__packed__)),使得结构体定义紧凑,但是会牺牲读取速度。
4.内存对齐(分为自对齐和结构体对齐)详见:
http://blog.csdn.net/andy572633/article/details/7213465
#pragma pack(n)用于提示结构体按照min(n,结构中最大位数)字节对齐。如:
#pragma pack(4) typedef struct { char buf[3]; word a; }kk; |
按照2字节对齐。
计算时画图,第一位是0,然后计算按照几位对齐,计算当前自对齐的值,然后计算比自对齐值最小的对齐值的倍数值就是sizeof的值(如上自对齐值为:3,对齐值为2,最小的倍数就是4,因此sizeof kk == 4)。
5.位域/位段:信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
详见:http://www.cnblogs.com/pure/archive/2013/04/22/3034818.html
位段内的类型必须是unsigned int/int/signed int:
a.一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bs { unsigned a:4 unsigned b:5 /*从下一单元开始存放,8位字节无法再存放5位*/ unsigned c:4 }
|
b.位域的长度不能大于一个整形的长度(例如32位机器,一个整形四个字节,一个字节8位,共计32位,因此不能int i:33)。
c.位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k { int a:1 int :2 /*无位域名,该2位不能使用*/ int b:3 int c:2 }; |
6.联合体union:内部的成员使用的是相同的内存地址,大小由其最大的类型决定。联合体的采用{}初始化时只能给出其第一个变量的类型值,如果给出其他类型,会转换为第一个类型的值。
Chap 11 动态内存分配
1.calloc与malloc的不同之处有:一、calloc会初始化内存位0;二、calloc申请的参数是元素的个数和每个元素占用的字节,用这两个可以计算出总大小。
2.Realloc用于修改原有的内存。
Chap 12 使用结构和指针
单链表、双链表
Chap 13 高级指针话题
1.函数指针的主要用途:
a.回调函数:例如函数接受一个函数指针用于指定比较器。Void Func(int* p, int(*p)(void))
b.转换表(函数指针数组):
Double add(double l, double r)/Double minus(double i, double r)...
转化表:double (*oper[])(double l,double r) = {add,minus};
调用:result = oper[op](l,r);
Chap 14 预处理器
1.预定义的符号:__FILE__源文件名、__LINE__文件行数、__DATE__文件编译日期、__TIME__文件编译时间、__STDC__编译器遵循ANSI值为1,否则未定义
Chap 15 输入、输出函数
Chap 16 标准函数库
Chap 17经典抽象数据类型
1.二叉树:树的每个节点之多有两个子节点。
2.二叉搜索树:节点的值比其左节点的值大,比起右节点的值小(没有相等值的节点)。
3.递归、尾递归、迭代:
int fibonacci(int n) { if (n == 0) return 0; else if (n == 1) return 1; else return fibonacci(n-1)+fibonacci(n-2); } 尾递归: int fibonacci_cycle(int n) { int fib; int f0 = 0; int f1 = 1; if (n == 0) return f0; else if (n == 1) return f1; else { for (int i = 2; i <= n;i++) { fib = f0 + f1; f0 = f1; f1 = fib; } return fib; } } |
尾部递归示意图:
需要额外传入两个参数,以空间换时间。普通递归需要有n个调用栈信息,而尾递归将参数诸个传入最终调用的那次计算,不用保存调用栈信息,因此快。
Chap 18 运行时环境
1.静态变量初始化:
int static_variable = 5; 编译成汇编: .data 进入数据区 .global _static_variable 定义全局变量 _static_variable: 为全局变量赋值 .long 5 |
2.堆栈帧
void f(){} 函数由函数序(准备工作,例如为局部变量准备栈)、函数体(具体工作)和函数跋(收尾清理工作)构成。 编译成汇编: .text 进入代码区 .globl _f 定义全局函数名 _f: link a6,#-88 创建一个88个字节空间的堆栈帧,用于存放临时变量 Moveml #0x3cfc,sp@ |
未完待续(关于汇编,感觉挺好,暂时没时间看了)...