这篇博客是我学习指针之后的自我检测,如果你敢兴趣的话,让我们一起来学习吧。
一、内存和地址
1.内存和指针的联系
指针和内存是不可分割的,因为通过指针可以访问内存空间。
那我们在学习指针之前,就先来了解一下内存,内存就是计算机中用来存储数据的非常大的空间。如果cpu要处理数据的话,得通过数据总线从内存中读取数据。可是内存很大,短时间内找不到要处理的数据,这时就涉及到内存的管理了。
为了快速精确的找到所需要的数据,将内存分为一个个内存单元,大小为一个字节,并将每个内存单元编号,这个编号就是内存单元的地址,在c语言中也叫指针。

## 2.地址
在计算机中,CPU要从内存中读取数据,先有
由控制总线发出读信号,然后CPU将要读取数据的地址通过地址总线传递给内存,就能准确找到对应的内存空间,最后经数据总线交给CPU。
而CPU处理数据后要返还给内存也一样,由控制总线发出写信号,然后CPU将要读取数据的地址通过地址总线传递给内存,就能准确找到对应的内存空间,最后经数据总线交给内存。
二、指针变量和地址
1.分析代码,认识&a,*p和p都代表什么意思。
#include <stdio.h>
int main(){
int a=20;
int *p=&a;
*p=666;
return 0;
}
其中,取地址操作符&a的作用就是取出a的地址。
p就是一个指针变量,用来存放a的地址,指针类型为int*类型。
int*中的*表示p是一个指针变量,int则表示p指向的变量类型是int类型。
解引用操作符*p的作用是通过地址得到p指向的对象。
2.指针变量的大小。
指针变量的就是用来存放地址的变量,他的大小与存储地址所需要的内存空间大小有关,但是不管指针的类型是什么,他们的地址都只与环境有关。在32位环境下,地址占32bit,指针变量大小为4个字节;相同的,在64位环境下,地址占64bit,指针变量大小为8个字节;
三.指针变量类型的意义。
(1)指针变量类型决定了指针解引用时的访问权限(一次可以操作的字节数);
#include <stdio.h>
int main(){
int a=0x11223344;
int* pa=&a;
*pa=0;
return 0;
}

执行*pa=0后:
说明int型指针解引用可以访问4个字节
#include <stdio.h>
int main(){
int a=0x11223344;
char* pb=&a;
*pb=0;
return 0;
}

执行*pb=0
后:
说明char型指针解引用可以访问1个字节
(2)指针变量类型决定了指针加减整数时一次可以跳过的字节数;
#include <stdio.h>
int main(){
int a=10;
int* pa=&a;
char* pb=&a;
printf("&a=%p\n",&a);
printf("&a+1=%p\n",&a+1);
printf("pa=%p\n",pa);
printf("pa+1=%p\n",pa+1);
printf("pb=%p\n",pb);
printf("pb+1=%p\n",pb+1);
return 0;
}
执行后:
说明int*型指针变量+1跳过4个字节,char*型指针变量+1跳过1个字节。结论:pa+n:int*型指针pa跳过n*sizeof(int)个字节;pb+n:char*型指针跳过n*size(char)个字节。
(3)void*型指针这是无具体类型的指针,当不知道某指针用来存放什么类型的地址时用。
int a=10;
char* p=&a;
下面的代码会报警告,因为&a是int*类型的,强行赋给char*型指针就警告了。
四.const修饰变量。
int a=10;
a=20;
printf("%d\n",a);
结果为40.
const int a=20;
a=40;
printf("%d\n",a);
//这段代码错误,因为const修饰的变量具有常属性,是常变量,其值不能改变。
int a=20;
int* p=&a;
*p=40;
printf("%d\n",*p);
结果为40.
const修饰变量。
const修饰*左边,*p即指针指向的对象不能改变,但p即指针的指向(存放的地址不变);
int a=10;
int b=20;
int const * pa=&a;
*pa=15;//error
pa=&pb;//正确
const修饰*右边,*p即指针指向的对象可以改变,但p即指针的指向(存放的地址可以改变);
int a=10;
int b=20;
int * const pa=&a;
*pa=15;//正确
pa=&pb;//error
若是const修饰*两边,则既不能改变*p,也不能改变p;
int a=10;
int b=20;
int const * const pa=&a;
*pa=15;//error
pa=&pb;//error
五.指针运算。
1、指针加减整数
运用:用指针的方法打印数组
原理:指针解引用和指针加减整数以及数组是连续的一块内存空间。
#include <stdio.h>
#include <string.h>
int main(){
int arr[5]={1,2,3,4,5};
int i=0;
int* p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
for(i=0;i<sz;i++){
printf("%d ",*(p+i));
}
return 0;
}
#include <stdio.h>
#include <string.h>
int main(){
int arr[5]={1,2,3,4,5};
int i=0;
int* p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
for(i=0;i<sz;i++){
printf("%d ",*p);
p++;
}
return 0;
}
2.指针减指针
指针减指针的绝对值表示两指针之间整数的数量
运用:写一个函数,计算字符串长度(字符串结束标准是\0)
#include <stdio.h>
#include <string.h>
size_t my_strlen(char* p){
char* start=p;
while(*p!='\0'){
p++;
}
return p-start;
}
int main(){
char arr[]="abcdef";
size_t len=my_strlen(arr);
printf("%zd\n",len);
return 0;
}
3.指针的关系运算
就是进行数组的比较
应用于:打印整形数组
#include <stdio.h>
int main(){
int arr[5]={1,2,3,4,5};
int sz=sizeof(arr)/sizeof(arr[0]);
int * p=arr;
while(p<arr+sz){
printf("%d ",*p);
p++;
}
return 0;
}
六.野指针
1.成因:
(1)指针不初始化
int a=10;
int* p=&a;//正确
int*p;//指针未初始化,是野指针
*p=20;//非法访问
可以想作喝醉酒后随便找一个房间就进去住了
(2)指针越界访问
int main(){
int arr[5]={1,2,3,4,5};
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
int* p=arr;
for(i=0;i<=sz;i++){ //error,野指针,指针越界访问
printf("%d ",*(p+i);
}
return 0;
}
(3)返回了已经失去访问权限的局部变量的地址
#include <stdio.h>
int* test()
{
int n = 100;//n是局部变量,只能在该函数范围内使用,出范围就失去了这块内存的访问权限,返还给操作系统
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);//p是野指针,非法访问
return 0;
}
可以想作昨天晚上去开的酒店房间,退房后今天就不能再住了。
2.如何避免野指针
(1)指针要初始化
知道指针所指对象的地址就正常初始化,不知道就赋值空指针。
可以想作不初始化的指针是条没有主人的疯狗,用NULL当绳子拴起来。
而且!!!空指针不能使用
(2)指针不要越界访问
(3)指针所指的内存空间不需要使用时,将指针赋值NULL,在使用指针前要判断是否为空指针
(4)避免返回局部变量的地址
七、assert断言
assert()是头文件<assert.h>定义的宏
作用:可以用来判断是否符合assert()里面的表达式(如判断指针是否为空指针),若表达式为真则正常运行,为假就报错,显示error所在的文件名和行号。
优点:不需要修改代码就可以开启或关闭assert();当已经判断代码没有问题,可以在#include <assert.h>前面加上#define NDUG,这样assert()被禁用;
assert()在debug版本下可以使用,但release版本下也被禁用。
八、指针的传值调用和传址调用
区别:传值调用只能使用主调函数传过来的值,没有修改权限,而传址调用可以改变主调函数中的值。
#include <stdio.h>
int add(int x,int y){//x和y是形参,形参是实参的临时拷贝,改变形参,实参不会改变的
return x+y;
}
int main(){
int a=10;
int b=20;
int c=add(a,b);//传值调用
printf("%d\n",c);
return 0;
}
//写一个函数,交换a和b的值
#include <stdio.h>
void swap(int* x,int* y){\\这里虽然也是形参,但是传过来的是实参的地址,通过指针可以找到实参的值并改变
int tem=0;
tem=*x;
*x=*y;
*y=tem;
}
int main(){
int a=10;
int b=20;
printf("交换前a=%d b=%d\n",a,b);
swap(&a,&b);//传址调用
printf("交换后a=%d b=%d\n",a,b);
return 0;
}
28万+

被折叠的 条评论
为什么被折叠?



