1. 地址算数运算示例
指针算数运算 : int *p, array[5]; p = array; p 指向一个 int 数组元素, p + i 的地址时数组中第 i 个元素的地址, 即 p + i 指向 第 i 个元素;
存储分配示例函数 :
-- char *alloc(int n) 函数 : 传入分配的字符个数, 返回连续字符存储单元指针, 这个指针可以存储 n 个字符元素;
-- int afree(char *p) 函数 : 释放分配的内存空间;
-- 缺陷 : 分配内存的时候, 有一个偏移量, 偏移量的大小代表已经分配了多少内存, 释放内存必须按照分配内存的顺序释放, 否则偏移量就会乱;
-- 内存分配原理 : 设置一个大数组, 内存分配就分配这个数组的中的空间, alloc 和 afree 函数操作的是指针, 不是数组, 因此这个数组可以隐藏, 将数组定义为static 类型, 那么在其它文件中, 不能访问该数组, 设置一个偏移量, 当分配 n 个元素, 偏移量就加上 n, 当偏移量 等于 数组大小, 说明内存全部分配完毕;
-- 偏移量设计 : 设置一个偏移量, 偏移量始终指向大数组的下一个空闲的元素地址, 当分配内存的时候, 通过计算 数组首地址 + 数组长度 - 偏移量 >= 分配大小 , 成立的话就可以分配内存, 分配内存就是将偏移量 加上 分配大小; 释放内存的时候, 就将偏移量 指向 释放内存的指针的首地址, 因此 要保持后进先出的次序;
代码 :
/************************************************************************* > File Name: alloc_afree.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年03月17日 星期一 19时34分08秒 ************************************************************************/ #include<stdio.h> //用于内存分配载体的大数组大小 #define ALLOCSIZE 1000 /* * 该数组就是用于内存分配的主体, * 设置为static , 意味着只能在本文件中访问, 在其它文件中不能访问 */ static char alloc_buf[ALLOCSIZE]; /* * 将alloc_buf 数组的首地址赋值给allocp字符指针 * 对allocp 进行算数运算, 每次加减都是 char * 运算数 * allocp的值就可以代表所分配内存的首地址 */ static char *allocp = alloc_buf; /* * 分配n个char类型数组的内存, * 如果分配成功, 返回分配的内存的指针, * 如果分配失败, 返回0 */ char *alloc(int n) { //如果大数组剩余的空间可以分配, 那么就进行分配 if(alloc_buf + ALLOCSIZE - allocp >= n) { //分配空间, allocp 指向下一个空间的内存首地址 allocp += n; //返回分配的空间首地址 return allocp - n; }else //如果数组剩余空间不足, 返回0 { return 0; } } /* * 释放分配的内存 * 释放内存就是将allocp 指针地址指向 要释放的内存指针首地址 */ void afree(char *p) { //释放内存的前提是 内存必须是大于数组首地址, 小于数组尾地址 if(p >= alloc_buf && p < alloc_buf + ALLOCSIZE) { allocp = p; printf("allocp = %p \n", allocp); } } int main(int argc, char **argv) { char *p1; char *p2; char *p3; char *p4; //打印数组首地址 printf("alloc_buf = %p \n", alloc_buf); //分配300个字符内存 p1 = alloc(300); printf("char *p1 = alloc(300), p1 = %p \n", p1); p2 = alloc(300); printf("char *p2 = alloc(300), p2 = %p \n", p2); p3 = alloc(300); printf("char *p3 = alloc(300), p3 = %p \n", p3); //上面已经分配了900了, 在分配就溢出了, 这里alloc()函数返回0 p4 = alloc(300); printf("char *p4 = alloc(300), p4 = %p \n", p4); afree(p4); afree(p3); afree(p2); afree(p1); }
执行结果 :
/************************************************************************* > File Name: alloc_afree.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年03月17日 星期一 19时34分08秒 ************************************************************************/ #include<stdio.h> //用于内存分配载体的大数组大小 #define ALLOCSIZE 1000 /* * 该数组就是用于内存分配的主体, * 设置为static , 意味着只能在本文件中访问, 在其它文件中不能访问 */ static char alloc_buf[ALLOCSIZE]; /* * 将alloc_buf 数组的首地址赋值给allocp字符指针 * 对allocp 进行算数运算, 每次加减都是 char * 运算数 * allocp的值就可以代表所分配内存的首地址 */ static char *allocp = alloc_buf; /* * 分配n个char类型数组的内存, * 如果分配成功, 返回分配的内存的指针, * 如果分配失败, 返回0 */ char *alloc(int n) { //如果大数组剩余的空间可以分配, 那么就进行分配 if(alloc_buf + ALLOCSIZE - allocp >= n) { //分配空间, allocp 指向下一个空间的内存首地址 allocp += n; //返回分配的空间首地址 return allocp - n; }else //如果数组剩余空间不足, 返回0 { return 0; } } /* * 释放分配的内存 * 释放内存就是将allocp 指针地址指向 要释放的内存指针首地址 */ void afree(char *p) { //释放内存的前提是 内存必须是大于数组首地址, 小于数组尾地址 if(p >= alloc_buf && p < alloc_buf + ALLOCSIZE) { allocp = p; printf("allocp = %p \n", allocp); } } int main(int argc, char **argv) { char *p1; char *p2; char *p3; char *p4; //打印数组首地址 printf("alloc_buf = %p \n", alloc_buf); //分配300个字符内存 p1 = alloc(300); printf("char *p1 = alloc(300), p1 = %p \n", p1); p2 = alloc(300); printf("char *p2 = alloc(300), p2 = %p \n", p2); p3 = alloc(300); printf("char *p3 = alloc(300), p3 = %p \n", p3); //上面已经分配了900了, 在分配就溢出了, 这里alloc()函数返回0 p4 = alloc(300); printf("char *p4 = alloc(300), p4 = %p \n", p4); afree(p4); afree(p3); afree(p2); afree(p1); }
函数执行示例图 :
-- alloc()函数示例图 :
-- afree()函数示例图 :
指针初始化 : static char *allocp = alloc_buf, 将char数组的首地址赋值给char类型指针;
-- 初始化内容 : 0 或者 地址;
-- 地址限定 : 对指针初始化的地址, 该地址存储的数据的类型必须是该指针类型;
内存可用判断 : alloc_buf + ALLOCSIZE - allocp >= n;
-- 意义 : alloc_buf 是数组首地址, ALLOCSIZE 是数组大小, allocp是可用内存偏移量, alloc_buf + ALLOCSIZE -allocp 结果是可用的内存量, 如果可用内存大于n, 则可以赋值;
-- 如果内存不足 : 内存不足, 将0作为地址返回, C语言中设定 0 不是有效的数据地址, 0地址的数据为NULL, 返回0表示发生了异常事件;
指针整数转换特例 : 指针 和 整数 不能相互转换;
-- 通常情况 : 指针 和 整型 之间不能相互转换, 0 除外;
-- 特例 : 常量 0 可以赋值给指针, 指针 可以和 常量 0 进行比较, 这里注意是常量;
-- 0的特殊性 : NULL 可以代替 常量0, 常量0 是一个特殊值;
指针运算 :
-- 比较运算 : 两个指针都指向同一个数组中的元素, 那么两个指针之间的比较是有意义的, 指向两个不同数组元素的指针之间比较无意义;
-- 加减运算 : 指向数组元素的指针, 进行加减运算, 地址的计算按照 运算数 * 指针指向元素的大小 进行计算;
计算字符串长度示例 :
-- 代码 :
/************************************************************************* > File Name: strlen_pointer.c > Author: octopus > Mail: octopus_work.163.com > Created Time: 2014年03月17日 星期一 21时38分52秒 ************************************************************************/ #include<stdio.h> //计算字符串长度 int strlen(char *s) { //指针 p 记录首地址 char *p = s; //循环获取字符串最后的字符首地址 while(*p != '\0') p++; //字符串占用的内存地址个数 return p - s; } int main(int argc, char **argv) { char *c = "fuck you!!"; printf("length = %d \n", strlen(c)); return 0; }
-- 执行效果 :
octopus@octopus-Vostro-270s:~/code/c/pointer$ gcc strlen_pointer.c octopus@octopus-Vostro-270s:~/code/c/pointer$ ./a.out length = 10
指针差值类型 :
-- ptrdiff_t : 该类型定义在 stddef.h 头文件中, 表示两个指针之间的带符号的差值;
-- size_t : 该类型定义在 stdio.h 头文件中, size_t 可以作为 sizeof 返回的无符号整型;
指针运算一致性 : 指针运算会自动考虑其指向的元素的长度, p 指向一个类型元素 a, 不管 a 占多少字节, p++ 的下一个元素都指向下一个 同类型的元素;
指针之间的有效运算 : 除下面几种合法运算之外, 其它运算都是非法的, 但不会报错, 会警告;
-- 赋值 : 同类型指针之间的赋值运算;
-- 算数 : 指针与整数的加减运算;
-- 0相关 : 指针与0 的赋值运算, 比较运算;
2. 字符指针与函数示例
字符串常量 : 字符串常量是一个字符数组;
-- 字符串常量界定 : 字符数组以 '\0' 结束, 程序通过检查 NULL 字符找到字符串的结尾;
-- 长度大于1 : 字符串常量占据的存储单元 比 字符的个数 多1位, 这一位是 '\0';
常量字符串访问方式 : 通过指针进行访问, 指针指向常量字符串的第一个字符, 程序可以通过这个指针操作字符串常量;
字符串定义方式 :
-- 数组 : char array[] = "fuck"; array 存放 fuck 字符串 和 '\0', array 地址是字符串首地址;
-- 指针 : char *c = "fuck"; 将字符串的首地址赋值给指针c, 没有经过字符串复制;
-- 区别 : 数组 - array 指向的地址不能改变, 单个字符可以修改; 指针 - c 指向字符串常量, 可以被修改指向其它地址, 修改字符串内容没有意义, 这样会在创建一个字符串常量, 并将首地址赋值给指针;
示例代码 : 分别用数组 和 指针 用法 拷贝字符串, 字符串比较;
/************************************************************************* > File Name: string.c > Author: octopus > Mail: octopus_work.163.com > Created Time: Tue 18 Mar 2014 12:34:20 AM CST ************************************************************************/ #include<stdio.h> /* * 字符串拷贝 * 将 source[i] 赋值给 density[i], 每次循环 i++ * 当density[i] == '\0'的时候停止循环 */ void strcpy_array(char *density, char *source) { int i = 0; while((density[i] = source[i]) != '\0') i++; } /* * 字符串拷贝 * *density++ 是对*density地址指向的值赋值, 然后地址进行自增操作 * *source++ 是先取出source 指向的地址的值, 然后地址进行自增操作 */ void strcpy_pointer(char *density, char *source) { while(*density++ = *source++); } /* * s[i] == t[i] 前提下 s[i] = '\0' * 此时 返回 s[i] - t[i] * 如果返回0 */ int strcmp_array(char *s, char *t) { int i; for(i = 0; s[i] == t[i]; i++) if(s[i] == '\0') return 0; return s[i] - t[i]; } int strcmp_pointer(char *s, char *t) { for(; *s == *t; s++, t++) if(*s == '\0') return 0; return *s - *t; } int main(int argc, char **argv) { char *source = "octopus"; char density[10]; printf("strcmp_array = %d \n", strcmp_array(density, source)); printf("strcmp_pointer = %d \n", strcmp_pointer(density, source)); strcpy_pointer(density, source); //打印字符串, 使用 字符串首地址 替代 %s; printf("source = %s \n", source); printf("density = %s \n", density); }
运行结果 :
* 和 自增(减) 运算 :
-- *source++ : 上面的该表达式的意义是 执行 自增运算之前, source 指针指向的字符, 读取到该字符之后, 该指针指向的地址 +1;
-- *density++ = *source++ : source指针自增前, 现将其指向的字符 赋值给 density 之前指向的地址的字符, 然后将 source 指针 +1;
-- 入栈 : *p++ = val, 这是标准的入栈操作, 将val压入栈, 然后指针自增1, 注意, 这里最后一个栈多加了一个1, 然后出栈的时候要先减1 在取值;
-- 出栈 : val = *--p, 这是标准的出栈操作, 现将指针减一, 然后取出指针指向的数据, 因为指针总是指向首地址, 如果我们想要取出某一段的值, 先要将指针指向首地址才可以;