1. 指针是什么
- 指针是内存中一个最小单元(1个字节)的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量(存放在指针变量中的值都被当成地址处理)
总结: 指针就是地址,口语中说的指针通常指的是指针变量
#include <stdio.h> int main() { int a = 100; int * pa = &a;//pa是专门用来存放地址(指针)的,这里的pa就被称为指针变量 //指针变量在32位平台下是4个字节 //指针变量在64位平台下是8个字节 //int arr[10]; //printf("%p", &a); return 0; }
注:
- 经过仔细的计算和权衡,我们发现一个字节给一个对应的地址是比较合适的。
- 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电频(高电压)和低电频(低电压),就是(1或者0),那么32根地址线会产生2的32次方个地址,每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空间进行编址,64位机器同理。
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
#include <stdio.h> int main() { printf("%d\n", sizeof(char*)); printf("%d\n", sizeof(short*)); printf("%d\n", sizeof(int*)); printf("%d\n", sizeof(long*)); printf("%d\n", sizeof(float*)); printf("%d\n", sizeof(double*)); return 0; }
2. 指针变量的类型
char * pc = NULL;
short * ps = NULL;
int * pi = NULL;
long * pl = NULL;
float * pf = NULL;
double * pd = NULL;
这里可以看到,指针变量的定义方式是: type + *
其实:
char* 类型的指针变量是为了存放 char 类型变量的地址。
short* 类型的指针变量是为了存放 short 类型变量的地址。
int* 类型的指针变量是为了存放 int 类型变量的地址。
但是,指针变量的大小又和指针变量的类型无关,那么指针变量的类型的意义是什么呢?
2.1 指针变量±整数
#include <stdio.h> int main() { int a = 0x11223344;//0x开头的是16进制数字 int * pa = &a; char * pc = &a; printf("%p\n", pa);//010FFE7C printf("%p\n", pc);//010FFE7C printf("%p\n", pa+1);//010FFE80 printf("%p\n", pc+1);//010FFE7D return 0; }
总结: 指针变量的类型决定了指针向前或者向后走一步有多大(距离)。
2.2 指针变量的解引用
#include <stdio.h> int main() { int a = 0x11223344;//0x开头的是16进制数字 char * pa = &a; *pa = 0; return 0; }
#include <stdio.h> int main() { int a = 0x11223344; int* pa = &a; *pa = 0; return 0; }
总结: 指针变量的类型决定了对指针变量解引用的时候有多大的权限(能操作几个字节),比如: char* 的指针变量解引用就只能访问一个字节,而 int* 的指针变量解引用就能访问四个字节。
3. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1 野指针成因
- 指针未初始化
#include <stdio.h> int main() { int* p;//局部变量不初始化的时候,内容是随机值 *p = 20; printf("%d\n", *p); return 0; }
- 指针越界访问
#include <stdio.h> int main() { int arr[10] = { 0 }; int * p = arr; int i = 0; for (i = 0; i <= 11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; }
- 指针指向的空间释放
放在动态内存开辟的时候讲解,这里可以简单提示一下。
#include <stdio.h> int * test() { int a = 110; return &a; } int main() { int* p = test(); printf("%d\n", *p); return 0; }
3.2 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放,及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
#include <stdio.h> int main() { int a = 10; int* p = &a; int* ptr = NULL;//ptr是一个空指针,没有指向任何有效的空间,这个指针不能直接使用 //int* ptr2;//野指针 if (ptr != NULL) { //使用 } return 0; }
4. 指针运算
4.1 指针±整数
#include <stdio.h> int main() { int arr[10] = { 0 }; //不使用下标访问数组 int* p = &arr[0]; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { *p = i; p++;//p = p + 1 } p = arr; for (i = 0; i < sz; i++) { printf("%d ", *(p + i));//p + i } /*for (i = 0; i < sz; i++) { printf("%d ", arr[i]); }*/ return 0; }
#define N_VALUES 5 int main() { float values[N_VALUES]; float *vp; //指针+-整数;指针的关系运算 for (vp = &values[0]; vp < &values[N_VALUES]; ) { *vp++ = 0; } return 0; }
注:
//int arr[10] //int* p = arr; //*(p+i) == arr[i] //*(arr+i) == arr[i] //arr[i] == *(arr+i) == *(i+arr) == i[arr] int main() { int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", i[arr]);//[] 操作符 } //2+3 --> 3+2 //arr[i] --> i[arr] return 0; }
4.2 指针-指针
//地址-地址 //指针-指针 #include <stdio.h> int main() { int arr[10] = { 0 }; printf("%d\n", &arr[9] - &arr[0]);//9 printf("%d\n", &arr[0] - &arr[9]);//-9 return 0; } //指针-指针得到的数值的绝对值是指针和指针之间的元素个数
之前我们学习过如何通过自己的函数来实现计算字符串长度:
#include <stdio.h> int my_strlen(char* s) { int count = 0; while (*s != '\0') { count++; s++; } return count; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
#include <stdio.h> int my_strlen(char* s) { if ('\0' == *s) { return 0; } else { return 1 + my_strlen(s + 1); } } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
其实,这也能通过指针-指针来实现:
#include <stdio.h> int my_strlen(char* s) { char* start = s; while (*s != '\0') { s++; } return s - start; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
#include <stdio.h> int my_strlen(char* s) { char* start = s; while (*s)//a b c d e f \0 -> 0 { s++; } return s - start; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
#include <stdio.h> int my_strlen(char* s) { char* start = s; while (*s++) { ; } return s - start - 1; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
注:
#include <stdio.h> int main() { int arr[10] = { 0 }; char ch[5] = { 0 }; //指针和指针相减的前提是:两个指针指向了同一块空间 printf("%d\n", &ch[4] - &arr[0]);//err return 0; }
4.3 指针的关系运算
#define N_VALUES 5 int main() { float values[N_VALUES]; float *vp; for(vp = &values[N_VALUES]; vp > &values[0];) { *--vp = 0; } return 0; }
这段代码也可以这样写:
#define N_VALUES 5 int main() { float values[N_VALUES]; float *vp; for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) { *vp = 0; } return 0; }
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。