指针
1. 定义指针变量
类型* 指针变量名;
-
指针变量中只记录了内存中某字节的地址编号,我们把它当作一个内存块的首地址,当使用指针变量访问内存时具体访问多少个字节,由指针变量的类型决定。
char* p; // 能访问1字节 short* p; // 能访问2字节 int* p; // 能访问4字节
-
指针定义容易出错的一点
int* p1,p2,p3; // p1是指针变量,p2、p3是普通的int类型变量 int *p1,*p2,*p3; // p1、p2、p3都是指针变量 typedef int* intp; intp p1,p2,p3; // p1、p2、p3都是指针变量 因为intp是一个数据类型 // 就像int a,b,c; 一样 他们都是int类型
-
指针也是一个变量,默认值是随机的,为了安全(避免产生野指针),如果初始化值不知道赋什么值,至少先初始化为NULL;
-
指针变量赋值
指针变量 = 内存地址
int* p = malloc(4); // 把堆内存的地址赋值给指针变量 int* p = # //num变量的类型必须与p的类型相同
-
指针变量解引用
*指针变量
解引用就是根据该指针变量中存储的内存地址,去访问内存,访问的字节数由指针变量的类型决定。
-
空指针
也就是存储的是NULL的指针变量,操作系统规定不能访问该内存,访问就会段错误。
所以使用指针变量时,比如函数参数是指针变量,应该先判断是否为NULL,再解引用。
-
野指针
指针变量中存储的地址无法确定是否为合法地址。
一般野指针都是认为创造的,那么如何避免?
-
及时初始化指针变量,要么赋值合法内存地址,要么NULL;
-
不返回局部变量、块变量的地址,因为当函数执行完毕局部变量和块变量就被销毁;
-
与堆内存配合的指针变量,当堆内存被释放、销毁,该指针变量要及时的赋值为NULL,因为只是free的话只是释放掉指针所指的内存空间,自己本身并未置空。
野指针比空指针危害大很多,具有潜伏性、随机性,使用指针是需多留意。
-
-
指针的进步值
前面说到解引用时不同类型的指针变量访问的内存字节数不一样,他们实际访问的字节数就叫做指针变量的进步值。
-
指针运算
指针+n = 指针所代表的整数+进步值*n 指针-n = 指针所代表的整数-进步值*n 指针1-指针2 = (指针1所代表的整数-指针2所代表的整数)/进步值
#include <stdio.h> int main() { int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p1 = &arr[0]; int *p2 = &arr[9]; printf("%p %p\n", p2, p1); printf("%d %d %d \n", p2 - p1, *(p1 + 1), *(p2 - 1)); } //000000000061FE04 000000000061FDE0 //9 2 9
-
数组与指针
-
数组名就是数组内存块的首地址,它是个常量地址,所以它作函数的参数时,才能蜕变成指针变量。
-
指针变量可以使用[]解引用,数组名也可以*遍历,它们是等价的。
#include <stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; for(int i=0; i<10; i++) { printf("%d %d\n",arr[i],*(arr+i)); } } 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10
-
数组名是常量,而指针是变量。
-
指针变量有它自己的存储空间(存储数组地址),而数组名就是地址,它没有存储地址的内存。
-
指针变量与它的目标内存是指向关系,而数组名与它的目标内存是映射关系。
-
-
通用指针
一些通用的函数,它们的参数可能是任意类型的指针,但编译器规定不同类型的指针不能进行赋值,为了兼容各种类型的指针,C语言中设计了void类型的指针,它能与任意类型的指针互相转换,
它能解决不同类型的指针参数的兼容性问题。void* p1; int* p2 = p1; void* p3 = p2;
void类型的指针变量的进步值是1(字节)。
void类型的指针变量不能解引用 ,必须转换成其它类型的指针才能解引用。
几个通用操作函数
#include<strings.h> void bzero(void *s, size_t n); 功能:把内存块s的n个字节,赋值为0。 #include<string.h> void *memset(void *s, int c, size_t n); 功能:把内存块s的n个字节,赋值为c(0~255) void *memcpy(void *dest, const void *src, size_t n);//const 只读 功能:从src内存块拷贝n个字节的内容到dest内存块 int memcmp(const void *s1, const void *s2, size_t n); 功能:比较s1和s2内存块的n个字节 s1 > s2 返回1 s1 < s2 返回-1 s1 == s2 返回0
实现:
void my_bzero(void* s,size_t n) { if(NULL == s || 0 == n) return; for(int i=0; i<n; i++) { *(char*)(s+i) = 0; } } // 方便后续进行链式调用 void* my_memset(void* s,int c,int n) { if(NULL == s || 0 == n) return NULL; for(int i=0; i<n; i++) { *(char*)(s+i) = c; } return s; } void* my_memcpy(void* dest,void* src,size_t n) { if(NULL == dest || NULL == src || 0 == n) return NULL; for(int i=0; i<n; i++) { *(char*)(dest+i) = *(char*)(src+i); } return dest; } int my_memcmp(void* s1,void* s2,int n) { if(NULL == s1 || NULL == s2 || 0 == n) return 0x80000000; unsigned char* p1 = s1; unsigned char* p2 = s2; for(int i=0; i<n; i++) { if(p1[i] > p2[i]) return 1; if(p1[i] < p2[i]) return -1; } return 0; }
-
二级指针
就是存储指针变量内存地址的变量。
类型 二级指针 = &一级指针;
//类型必须相同
-
解引用
*二级指针 此时它等价于一级指针
**二级指针 此时它等价于 *一级指针
int num;
int* p =&num
int** p=&p;
#include <stdio.h> int main() { int num = 1234; int num1 = 4567; int* p = # int** pp = &p; *pp = &num1; // p = &num1; printf("%d\n",*p);/4567 **pp = 123456789; // num1 = 123456789; printf("%d\n",num1); /* num address=0xbf89e93c num=1234 num1 address=0xbf89e940 num1=4567 p=0xbf89e93c *p=1234 &p=0xbf89e944 pp=0xbf89e944 **pp=1234 &pp=0xbf89e948 *pp=0xbf89e93c *p=4567 p=0xbf89e940 num1=123456789 */ }
-
-
指针数组
由指针变量构成的数组,也可以说它的身份是数组,成员是指针变量。
类型* 数组名[n];
int * arr[10];
//就等于定义了10个指针变量
- 可以来构造不规则数组和字符串数组
-
数组指针
专门指向数组的指针变量,它的进步值是整个数组的字节数。
类型 (*指针变量名) [n];
#include <stdio.h> int main(int argc,const char* argv[]) { int arr[4][5] = { {11,12,13,14,15}, {21,22,23,24,25}, {31,32,33,34,35}, {41,42,43,44,45} }; int (*p)[4] = (void*)arr; for(int j=0; j<5; j++) { for(int i=0; i<4; i++) { printf("%d ",p[j][i]); } printf("\n"); } } /* 11 12 13 14 15 21 22 23 24 25 31 32 33 34 35 41 42 43 44 45 */
就好像行指针一样,一次指向n个元素,加1指向下一个n个元素。
-
函数指针
专门存储函数地址(函数在text内存的首地址)的指针变量。
1、先确定指向的函数的格式(函数声明)。
2、照抄函数声明。
3、用小括号包含函数名。
4、在函数名前加*
5、在函数名末尾加_fp,防止命名冲突。
6、用函数名给函数指针赋值后,函数指针就可以当作函数调用了。
#include <stdio.h> void func(void) { printf("..."); } int main() { void (*func_fp)(void) = func;//函数名就是首地址 func_fp(); }