目录
指针根本上就是一个值为内存地址的变量,如int类型是整型变量,char类型是字符变量,指针变量就是地址
程序运行时会在内存中分配一个一个的空间,一个空间叫做一个内存单元,一个内存单元的大小是一个字节
每一个内存单元都有一个编号,这个编号就是它的地址,找到了地址就可以通过它访问这个空间
指针变量
一个整型变量是四字节,一个字符变量是一字节,一个指针在32位平台的大小是四字节,在64位平台是八字节
如是32位的cpu,那么它就有32根物理地址线,假设它通电产生高低电压那么将这个电信号转换成数字信号就会产生0和1,那么他们的排序方式就有2^32个,就有2^32个字节,就是4GB的空间
而64位,就有64根物理地址线, 它就有2^64个字节,也就是8GB大小的空间
在32位平台中这些指针变量的大小都是四字节,在64位平台就是8字节
p=&pa 的意思是把a的地址给p,也就是p指向a的地址
b=*p 表示b指向p的值,p通过*解引用指向pa的地址从而找到那个值
这两句又相当于 b=pa
指针声明
int i=0; 声明整型类型
char c='c'; 声明字符类型
int* a=&i;声明一个整型类型的指针,指针变量a的类型是int*
a说明它是指针,*a是int类型
指针类型决定了指针进行解引用时,能够访问的内存大小是多少
int *a *a可以访问四字节
char *c *c可以访问一字节
#include<stdio.h>
int main()
{
int ch = 0x11223344;
int hc = 0x11223344;
int* i = &ch;
char* c = &hc;
*i = 0;
*c = 0;
return 0;
}
产生了两种结果可以发现,int类型访问四字节,char类型访问一字节(使用小端机器,数字的高字节储存在高地址中)
指针类型决定了,它进行解引用时一次访问多少字节
整型类型+1,在空间中就跳过了四字节
字符类型+1,就跳过了一字节
浮点型+1,跳过四字节,但是编译器会把这个数据当作浮点型、
int a
char* pa=&a 取出的是int*要把它放到char*中要进行强制类型转换 (char*)&a
野指针
野指针产生的原因:
指针未初始化,指针越界访问,指针指向的空间已经释放
指针未初始化时指针指向的位置是不可知的,随机的
#include<stdio.h>
int main()
{
int* p;
*p = 10;
return 0;
}
这时存入的值就是危险的不可控的
指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 12; i++)
{
p++;
}
return 0;
}
当i大于10,p++后的地址就是野指针了
int *test()
{
int a=10;
return &a;//返回地址后这个地址就已经销毁了
}
int main()
{
int* p=test();
printf("haha\n")//使用打印函数后就会把test的战争空间破坏,最后导致*p成为野指针
printf("%d\n",*p);
return 0;
}
只要是返回临时变量的地址,都会存在问题(除非这个变量出了这个范围不销毁)
避免野指针
指针初始化时不知到给什么地址可以给一个空指针NULL(专门初始化指针)
内存有一部分叫内核区专门给操作系统使用是不能给用户使用
0地址其实就是用户不能使用的地址,使用这种地址程序会崩掉
int* p=NULL;
*p现在是没有地址的没有指向任何有效地址
直接给*p赋值的话是很危险的,应该在p不等于空指针时再使用
小心指针越界
当指针指向空间不能用时要把它置空
避免返回局部变量的地址
使用之前检查有效性
指针运算
++的优先级高于*
*p++=*p
++先使用再加加,假如*p是0那么先使用它,再加加
- 指针减指针
条件
两个指针指向同一个空间,类型也要相同
指针减指针的绝对值得到的是两个指针之间的元素个数
- 指针加
- 指针减
实际运用
用函数求字符串长度
int my_strlen(char* ch)
{
int count = 0;
while (*ch++ != '\0')
count++;
return count;
}
int main()
{
char ch[] = { "abcdef" };
printf("%d\n", my_strlen(ch));
return 0;
}
- 库函数的写法
int my_strlen(const char* ch)
{
//const 修饰*c
const char* c = ch;
while (*c++);//\0的阿斯克码值是0为假
return c - ch - 1;//最后一位把\0也算上了,用尾地址减首地址再减去\0
}
int main()
{
char ch[] = { "abcdef" };
int st = 0;
st = my_strlen(ch);
printf("%d\n", st);
return 0;
}
- 比较指针
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
//把N_VALUES的地址赋给指针变量VP
for (vp = &values[N_VALUES]; vp > &values[0]; )
{ //前置减减先减再使用
*--vp = 0; //前置--
}
return 0;
}
这种写法在大部分编译器是可以运行的,但是尽量避免这样写,因为并不保证它可以执行
允许指向数组元素的指针与指向数组最后一个元素后面的拿个内存位置的指针比较,但是不允许与指向第一个元素之前的拿个内存位置的指针进行比较
允许向后越界的指针去比较,但不允许与第一个地址之前的指针去比较,虽然是越界了,但是没有去访问它的地址,没有解引用
指针和数组
指针是一种变量,大小一般是四字节或八字节
数组是一组相同类型元素的集合,可以放多个元素,大小是取决于元素的个数和元素类型
数组的数组名是数组首元素的地址,地址可以放在指针变量中
取数组名时取出的是整个数组的地址
sizeof(数组)计算的是整个数组的大小,单位是字节
可以通过指针访问数组
[ ]是一个操作符,i和arr是[ ]这个操作符的操作数
arr[i]编译器是算不了的,会把它转换成*(arr+i)
所以 arr[i]->*(arr+i) 因为加法支持交换律所以又可以变成 *(i+arr)->i[arr]
二级指针
一个指针的地址存到另一个指针变量
int main()
{
int a = 10;
int* pa = &a;
//pa指向a的地址
int** ppa = &pa;
//ppa指向pa而pa指向&a
return 0;
}
**ppa 通过 *pa 找到 a,然后对 pa 进行解引用操作,*pa 找到的就是 a
指针数组
字符数组存放字符
整型数组存放整型
指针数组存放指针
int arr1[5] 整型数组有五个元素每个元素都是整型
char arr2[5] 字符数组有五个元素每个元素都是字符
int* arr3[5] 整型指针数组有五个元素每个元素都是指针
int(*arr)[5] 表示arr指向有五个int类型的数组
因为[ ]的优先级比*大所以要加括号