读书笔记2:
第六章
指针
-
内存
计算机的内存由数以亿万计的位(bit)组成,每个位可容纳0或1,由于一个所能表示的值的范围太有限,因此单独的位用处不大,通常将许多位合成一组作为一个单位
比如:
这些位置的每一个被称为字节(byte),每个字节都包含了存储一个字符所需要的位数,许多现代机器上,每个字节包含8个位,可存储无符号值0~255或有符号值-128~127,每个字节通过地址来标识
为了存储更大的值,我们把两个或更多个字节合在一起去作为一个更大内存单位,许多机器以字为单位存储整数,每个字一般由2字节或4字节组成,下图所表示内存位置与上图相同
尽管一个字包含了4字节,但它仍然只有一个地址,至于它的地址是最左边那个字节的位置还是最右边字节的位置,不同机器有不同的规定
我们对两件事很有兴趣:
-
内存中的每个位置有一个独一无二的地址标识
-
内存中的每个位置都包含一个值
-
地址与内容
-
下面显示了内存中5个字的内容
如果你记住了一个值的存储地址,以后就可以根据这个地址取得这个值,但是记住地址太笨拙了,所以高级语言提供的特性之一就是通过名字来访问内存位置
这些名字就是我们所称的变量,你必须记住,即名字与内存位置之间的关联并不是硬件来提供的,是由编译器为我们实现的,所以硬件仍然通过地址访问内存地址。
-
值和类型
下面是这些变量的声明
int a = 112,b = -1;
float c = 3.14;
int *d = &a;//赋值a的地址
int *e = &b;//赋值b的地址
-
指针变量的内容
变量d和e是指针而不是整型或浮点数的值
-
间接访问操作符(*)
d是一个指向整型的指针,对他解引用操作将产生一个整型值,类似的,对float*今昔你个间接访问将产生一个float型的值
表达式 | 右值 | 类型 |
---|---|---|
a | 112 | int |
B | -1 | int |
c | 3.14 | float |
d | 100 | int* |
e | 108 | float* |
*d | 112 | int |
*e | 3.14 | float |
-
指针的指针
int a = 12;
int *b = &a;
int **c = &b;
表达式 | 相当的表达式 |
---|---|
a | 12 |
b | &a |
*b | a,12 |
c | &b |
*c | b,&a |
**c | *b,a,12 |
-
指针表达式
int ch = 1;
int *cp = &ch;
printf("%d",*cp+1);//*的优先级高于+,结果为2
printf("%d",*(cp+1));//指向下一个int型(未定义)
printf("%d",*cp++);//++的优先级高于*,1
printf("%d",++*cp++);//2
-
算术运算
-
指针 +- 整数
类似上面指针表达式的形式
-
指针 - 指针
只有当两个指针指向同一个数组中的元素时,才允许从一个指针减去另一个指针(绝大多数编译器不会检查指针表达式的结果是否位于合法的边界之内,因此,程序员要负起责任)
两个指针相减的类型时ptrdiff_t,有符号整数类型,它运算结果的值是两个指针在内存中的距离,下面是它如何作用于某个类型的
p2-p1指的是两个指针的差值(20)将除以每个元素的长度(4),所以他们的结果将是5
第八章
数组
-
在C中,几乎所有谁用数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址,它的类型取决于数组元素的类型,如果它们是其他类型,那么数组名的类型就是"指向其他类型的常量指针"
-
注意:这个值是指针常量而不是指针变量,指针常量是不可修改的
指针常量所指向的是内存中数组的起始位置,如果修改这个指针常量,唯一可行的就是吧整个数组移动到内存的其他位置,但在程序完成链接之后,内存中数组的位置是固定的,所以程序运行时,在想移动数组就已经晚了,因此,数组名的值就是一个指针常量
-
只有在两种情况下,数组名并不用指针常量来表示——当数组名左为sizeof操作符或单目操作符&的操作数时
int a[10];
int b[10];
int *c;
...
c = &a[0];
c = a;//这条语句和上面的语句执行的任务是一样的
*(b+3)//b[3]
arrray(subscript)
*(array + (subscript))
-
下标引用
int array[10];
int *ap = array + 2;
表达式 | 与其等价的表达式 |
---|---|
ap | array+2 |
*ap | *(array+2),array[2] |
ap[0] | *(array+2),array[2] |
ap+6 | array+8 |
*ap+6 | *(array+2)+6,array[2]+6 |
*(ap+6) | *(array+8),array[8] |
ap[6] | *(array+8),array[8] |
&ap | &(array+2),&array[2] |
ap[-1] | *(array+1),array[1] |
ap[9] | array[11] -- 非法的 |
2[array]//是合法的
*(2 + (array))
*(array + 2)
上面是有趣的小例子
-
指针与下标
下标绝不会比指针更有效率,但指针有时会比下标更有效率
//为了对下表表达式求值,编译器在程序中插入指令,取得a的值,并把它与整型的长度(也就是4)相乘,这个乘法需要花费一定的时间和空间
int array[10],a;
for(a = 0;a < 10;a += 1){
array[a] = 0;
}
//也存在乘法运算,1这个值必须与整型的长度相乘,然后再与指针相加,因为执行乘法运算你的都是两个相同的数(1和4),结果只在编译时执行了一次
int array[10],ap;
for(ap = array;ap < array + 10;ap++){
*ap = 0;
}
-
数组和指针
int a[5];
int *b;
声明一个数组时,编译器将根据声明所指定的元素数量维数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置
声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间,且未被初始化指向任何现有的内存空间
所以*a是合法的,*b是非法的,b++可以通过编译,a++因为是个常量而通过不了。
-
字符数组的初始化
这两个初始化看上去很像,但他们有不同的含义
char message1[] = "Hello"
char *message2 = "Hello";
-
指向数组的指针
int vector[10],*vp = vector;//合法
int matrix[3][10],*mp = matrix;//非法
//matrix并不是一个指向整型的指针,而是一个指向整型数组的指针
//所以我们应该声明一个指向整型数组的指针
int (*p)[10] = matrix;
//如果需要一个指针逐个访问整型元素而不是逐行再数组中移动
int *pi = &matrix[0][0];
//或
int *pi = matrix[0];
-
作为函数参数的多维数组
//1
int vector[10];
...
func1(vector);
//func1的函数原型
void func1(int *vec);
//或
void func1(int vec[]);
//2
int matrix[3][10];
...
func2(matrix);
//func2的函数原型
void func2(int (*mat)[10]);
//或
void func2(int mat[][10]);
//错误
//这个例子把mat声明为一个整型指针的指针,它和指向整型数组的指针并不是一回事
void func2(int **mat);
-
指针数组
下标引用的优先级高于间接访问
char const *keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while"
NULL//为了搜索时能够检测到表的结束
};
//存储在一个矩阵中
char const keyword[][9] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while"
};
指针数组的形式更紧凑些,这取决于指针所占用的空间是否小于每个字符串都存储于固定长度的行所浪费的时间
实际上,除非是非常巨大的表,否则这些差别非常之小。。。
第九章
字符串
-
字符串长度
返回类型是无符号整数
strlen();
不受限制的字符串函数
程序员必须保证目标字符数组的空间足以容纳需要复制的字符串,因为strcpy无法解决这个问题
-
复制字符串
strcpy();
-
连接字符串
strcat();
-
字符串比较
strcmp(char const *s1,char const *s2);
s1小于s2,strcmp函数返回一个小于零的值,s1大于s2,函数返回一个大于零的值,如果s1等于s2,函数就返回零
长度受限的字符串函数
len为处理字符的长度
char *strncpy(char *dst,char const *src,size_t len);
char *strncat(char *dst,char const *src,size_t len);
int strncmp(char const s1,char const *s2,size_t len);
字符串查找基础
-
查找一个字符串
char *strchr(char const *str,int ch);//该字符第一次出现的位置
char *strrchr(char const *str,int ch);//指向该字符最后一次出现的位置
-
查找任何几个字符串
char *strpbrk(char const *str,char const *group);//查找任何一组字符中任何一个字符在字符串中第一次出现的位置
-
查找一个子串
char *strstr(char const *s1,char const *s2);
高级字符串查找
-
查找一个字符串前缀
size_t strspn(char const *str,char const *group);
size_t strcspn(char const *str,char const *group);
-
查找标记
char * strtok(char *str,char const *sep);//
使用字符分类和转换函数可以提高函数的移植性