声明:文字版看不惯的请看每小节截图哦~
文章的末尾还有笔记的完整版长截图,可用电脑查看~
---------我是分割线-------
目录
//void*指针,可以理解为无具体类型的指针,可以接受任意类型地址
//1.指针没有初始化,局部变量如果不初始化,变量的值是随机的
//避免返回局部变量的地址,如上例子:3.指针指向的空间释放
//指针类型的意义
int main()
{
int a = 0x11223344;
char* pa = &a;//pa这里能存放的下a的地址吗?
//肯定能,X64的环境下指针变量都是8个字节,一定能放得下int类型的a
*pa = 0;//赋值后再内存中改动的只有一个字节,是因为pa是存放了地址的char*类型的指针,解引用之后的值就是char类型
//不同类型的指针解引用之后的类型是不一样的,这就是指针类型的意义;什么类型的指针解引用之后就是什么类型的数据,就占什么类型的大小空间
//但是如果是int*类型的指针可以直接访问4个字节的数据,而char*类型的指针只能访问一个字节。
//总结:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)
return 0;
}
//double*--8个字节
//short*--2个字节
int main()
{
int a = 10;
int* pa = &a;
char* pc = (char*) & a;//原本取出来的地址应该是int*类型,可以用(char*)强制性转换,以免编译器报警告
printf("pa=%p\n", pa);
printf("pa=%p\n", pc);
printf("pa+1=%p\n", pa+1);//+1相当于加了一个类型的空间大小
printf("pc+1=%p\n", pc+1);//总结:指针的类型决定了指针向前或者向后走一步有多大(距离)
return 0;
}
//每次运行后的地址可能不一样,看编译器
//以上这些有什么意义?
//第一个案例
int main()
{
int arr[10] = { 0 };
int i = 0;
int* p = &arr[0];
for (i = 0; i < 10; i++)
{
*p = 1;
p++;///*(p=p+1)*///或者arr[i] = 1;
//注意p++是地址加,不是数值加
}
//此时存放了地址的p以后指向了第9个元素的第一个字节的首地址
p = &arr[0];//要从头打印的话,得让p回到&arr[0]
for (i = 0; i < 10; i++)
{
printf("%d ",*p);//arr[i]
p++;//p找到一个元素打印出来之后++找到下一个元素再打印
}
return 0;
}
//第二个案例
//可以把int* p = &arr[0];一次访问00000001000000010000000100000001
//改成char* p = &arr[0];可以一次访问1一个字节00000001
//void*指针,可以理解为无具体类型的指针,可以接受任意类型地址
// 但是也有局限性:
//void*指针不能直接加减和解引用运算
int main()
{
int a = 10;
char* ch = 'w';
int* pa = &a;
char* pc = &a;//编译器报警告是因为两边的类型不一样
void* pv = &a;//不会报警告是因为void*可以接受任何类型的地址
char* pv2 = &ch;
*pv = 20;//err void*类型的指针不能直接进行解引用操作
pv++;//void*没有具体类型,所以不知道跳过几个字节
return 0;
}
//一般void*类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址
//const修饰指针
//常属性的意思(不变)
int main()
{
const int a = 10;//不能改a,但是本质还是变量,const仅仅只是在语法上做了限制,
//习惯上称a是常变量
a = 20;//不能赋值了,会报警告
printf("%d\n", a);
return 0;
}
int main()
{
const int a = 10;
int* pa = &a;
//但是一旦这样const int*pa=&a,就不能通过通过pa改a了
*pa = 0;//可以改a,是因为const仅仅只是在语法上做了限制,
//我们可以把a的地址给pa,然后通过pa改a
printf("%d\n", a);
return 0;
}
//const可以用来修饰指针,可以放在*的左边,也可以放在*的右边,
int main()
{
const int a = 10;
int const* p = &a;//和const*int p=&a效果是一样的,只要const在a的左边就是一样的
//int* const p = &a;//当const放在*的右边的时候限制的是p,而不是*p,所以*pa是可以改的
//*p = 0;//err
int b = 20;
p = &b;//ok
printf("%p", p);
return 0;
}
//p里面存放的是a的地址,同时p是变量,也有自己的地址;
//*p是p指向的空间(里面的值a)
//const*p=&a表示不能通过p来修改p指向的空间的内容,不会限制p,可以改变p里面存放的地址
//int*const p 限制的是p,不限制*p,可以通过p来修改p指向的空间的内容,
//但是不能改变p里面存放的地址,也就是不能指向其他变量了
//指针运算
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//使用指针打印数组的内容;
int* p = &arr[0];//初始化指针变量
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *p);
p++;//地址+一个类型的大小,int是一次加4个字节
//以上两行代码还可以合成:
//printf("%d ",*(p+i));p+i加的是i*sizeof(int),加的是i个整型的大小
}
return 0;
}
//指针-指针=整数
//指针+整数=指针
//可以类比为日期-日期=中间的天数,
//日期+天数=日期
//日期-天数=日期
int main()
{
//指针-指针==地址-地址
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
printf("%d\n", &arr[9] - &arr[0]);//指针-指针的绝对值得到的是指针之间的元素个数
//高地址-低地址得到的是正数
//低地址-高地址得到的是负数
//指针-指针运算的前提条件是:两个指针指向同一块空间;
char ch[20] = { 0 };
printf("%d\n", &ch[0]- &arr[0]);//算不出来,不是同一块空间
//这是因为这两个数组之间是否存在空隙不确定,
//结果(指针-指针的绝对值得到的是指针之间的元素个数)是按char来算还是int来算,这个是不确定的
return 0;
}
//指针-指针有什么用?
#include<string.h>
int my_strlen(char* s)//传过来的是第一个字符的地址
{
int count = 0;
while (*s != '\0')
{
count++;
s++;
}
return count;
}
//可以换一种写法:用\0的地址-第一个字符的地址
int my_strlen(char* s)
{
char* start = s;
//while (*s != '\0')
//也可以写成
while(*s)//只要是真就进入循环,\0的ASCII码值是0,退出循环
{
s++;
}
return s - start;//指针-指针
}
//
int main()
{
//strlen求字符串的长度-统计的是\0前面出现的字符的个数
int len = my_strlen("abcefg");//不包含\0
//传字符串给strlen时传的不是字符串的长度,而是第一个字符的地址
printf("%d\n", len);
return 0;
}
//指针的关系运算
//其实就是指针比较大小(地址比较大小)
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);//求元素个数10
//使用while循环打印arr的内容
int* p = &arr[0];
//arr是数组名,数组名其实是数组首元素的地址,arr<==>&arr[0]
while (p < arr+ sz)//只要p存放的地址小于它的地址+元素个数就保持循环
//&arr[0]+sz相当于跳过了10个元素,
//当p最后指向第11个元素的首地址时就等于arr+sz指向的地址,就跳出循环
{
printf("%d ", *p);
p++;
}
return 0;
}
//野指针
//野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
//全局变量如果不初始化,变量的值默认是0;静态变量不初始化,值默认也是0;
//因为全局变量和静态变量都是放在静态区的
//造成野指针的原因?
//1.指针没有初始化,局部变量如果不初始化,变量的值是随机的
int main()
{
int* ptr;//野指针
*ptr = 20;//非法访问内存了
return 0;
}
//2.指针越界访问
int main()
{
int arr[5] = { 0 };
int i = 0;
int* p = arr;
for (i = 0; i < 10; i++)//i<10导致指正越界访问
{
*p = 1;
p++;
}
return 0;
}
//3.指针指向的空间释放
int* test()
{
int a = 10;
return &a;
}
int main()
{
int*p= test();//a出了函数test()就把空间还给操作系统了
//这个时候虽然也能带回来a的地址,但是这个地址已经不属于a了,
//一旦p接收了这个地址就能野指针,因为它指向了一个不明确的位置
printf("hehe\n");
//如果在打印*p前先随便加别的东西破坏掉原先那个函数的栈帧空间,结果打印出来的*p就不再是10了。
printf("%d\n", *p);
//这个时候虽然还能打印出来10,是因为这个空间的内容还没有被覆盖
return 0;
}
//如果规避野指针?
//指针初始化;
//int a=10;
//int* pa = &a;//这里明确知道pa应该指向a,所以拿a到的地址初始化
//注:可能不知道给指针初始化谁的地址,直接一个NULL空,是0;
//int* p = NULL;//转换成空指针
//NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
int main()
{
int* p = NULL;//一旦这么写,那所有读者都应该知道这个指针没有明确的指向,不要乱用它
*p = 20;//一旦乱用程序就会报错了
return 0;
}
//野指针就像一只野狗,int* p = NULL相当于是给野狗栓在树上被警告人们不要碰他,当有人要碰他时就很危险,程序报错了;
//最后就是给野狗找到主人(初始化指针)
//小心指针越界
//指针变量不再使⽤时,及时置NULL,留着以后用,指针使⽤之前检查有效性
//只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL
int main()
{
int arr[10] = { 1,2,3,4,5,67,7,8,9,10 };
int i = 0;
int* p = &arr[0];
for (i = 0; i < 10; i++)
{
*(p++) = i;//后置++:先让*p等于i,p再++
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址!
if (p != NULL) //判断!!!!!!!!!!!
{
//...
}
return 0;
}
//避免返回局部变量的地址,如上例子:3.指针指向的空间释放
---------我是分割线-------
笔记完整版:(手机看比较模糊,可用电脑查看)
预知后事如何,请听下回分解......