大家好!今天小编给大家带来了有关指针的一些内容,
尤其是对小白很合适哦,不妨看看吧!
1.内存和地址
-
含义:1个内存单元大小是一个字节,即8个比特位。
-
理解:每个内存单元相当于一个学生宿舍,即一个字节空间。每个宿舍有8个床位=每个字节空间有8个比特位=八人间,每个人是一个比特位。
-
联系:计算机使用的是二进制语言,每个内存单元有自己的编号,内存编号=地址=指针
-
如何理解编址?
CPU访问内存空间 需要知道其地址 故要给内存进行编址
==》查寝 由于寝室很多 需要设计房间号 -
总结:内存会被分成一个个的内存单元,每个内存单元的大小是一个字节,每个内存单元都会给一个编号=地址=指针
2.指针变量和地址
2.1指针变量
int main()
{
int a=20;
int *p=&a;//int*是指针指向的类型,p是指针变量
return 0;
}
理解:存放地址的变量–>指针变量,指针变量是用来存放地址的,也默认为放在指针变量里的都是地址。
2.2解引用*
*p//解引用操作,间接访问操作 *p等价于a
3.指针变量的大小
- 说明:指针变量需要多大空间是取决于存放的是什么?存放的是地址,地址存放需要多大空间,指针变量的大小就是多大。
- 总结:
a.x86(32位):地址是32个0/1的二进制序列,存储起来需要32个bit位,即4个字节,指针变量大小就4个字节。
int main()
{
int a = 20;
int* pa = &a;
printf("%zd\n", sizeof(pa));//4
printf("%zd\n", sizeof(int*));//4
printf("%p\n", pa);//00EFFE4C
return 0;
}
b.x64(64位):地址是64个0/1的二进制序列,存储起来需要64个bit位,即8个字节,指针变量大小就8个字节。
int main()
{
int a = 20;
int* pa = &a;
printf("%zd\n", sizeof(pa));//8
printf("%zd\n", sizeof(int*));//8
printf("%p\n", pa);//00000097B9AFF954
return 0;
}
4.指针类型
int main()
{
int a = 0x11223344;
int* p = &a;
*p = 0;
return 0;
}
4.1结论:
指针类型决定了指针进行解引用操作的时候访问多大的空间。
int的指针解引用访问4个字节,char的指针访问1个字节。
4.2指针±整数
可以看见:char类型的指针+1跳过了1个字节,int类型的指针+1跳过了4个字节。
结论:指针类型决定了指针的步长,就是向前或向后走一步有多大距离。
type*p:
p+i是跳过了i个type类型的数据,相当于跳过了i*sizeof(type)个字节
int*p:
p+2是跳过了2个int*类型的数据,相当于跳过了2*sizeof(int)==8个字节
4.3void*指针
含义:无具体类型的指针,可以用来接受任意类型地址。
局限性:void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算。
如上,将int类型的变量地址赋值给char类型的指针变量,编译器警告:类型不兼容,使用void指针则不会有这种情况发生
4.4那么 void* 类型的指针到底有什么⽤呢?
⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据,在后续我们会讲解。
5.const修饰指针
5.1const修饰变量
总结:n本质是变量,只不过被const修饰后,不能被修改了,在语法上加了限制。那么如何间接修改n呢?绕过n,使用n的地址,就能修改啦!
理解:老默,我想吃鱼了—高启强借刀杀人
5.2const修饰指针变量
说明:
p是指针变量,里边存放了其他变量的地址,*p是指向的对象,p也有自己的地址&p。
两种情况:
1. const放在*的左边:const int p=&n;
2. const放在的右边:int * const p=&n;
int n = 10;
int m = 100;
const放在*的左边
const int* p = &n;
*p = 20;//err
p = &m;//ok
const放在*的右边
const int* p = &n;
*p = 20;//err
p = &m;//ok
总结:
1. const放在的左边,修饰的是指向的内容,指向对象的内容不能变,但指针变量本身的内容可以变。
限制的是p,不能通过p来改变p指向对象的内容,但p本身可以变,p可以指向其他对象。
2. const放在*的右边,修饰的是指针变量本身,指针变量的内容不能变,但是指针指向对象的内容可以变。
限制的是p,不能修改p本身的值,但p指向的内容可以通过p来改变。
6.指针运算
指针的基本运算有三种,分别是:
- 指针±整数
- 指针-指针
- 指针的关系运算
6.1指针±整数
数组在内存中是连续存放的,随下标增大,地址增大。
只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));//指针+整数
}
return 0;
}
6.2指针-指针
前提:两个指针指向了同一块空间
说明:得到的是指针之间元素的个数
strlen求字符串长度时,统计的是\0之前的字符个数;
数组名表示首元素地址,两种特例除外。
strlen模拟实现
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;//指针-指针:求字符串的长度
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
6.3指针的关系运算
指针的大小比较
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
7.野指针
7.1野指针成因
1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
如何规避?
a.指针初始化
int main()
{
int* p;//不初始化,相当于野狗没栓绳
int* p = NULL;//相当于把野狗拴在树上,NULL,标识符常量,0
return 0;
}
b.小心指针越界:不要超过申请的内存空间
c.指针不再使用及时置NULL,指针使用前检查有效性
d.避免返回局部变量的地址
8.assert断言
8.1前提:使用前需引用头文件<assert.h>
8.2含义:assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错。
8.3使用:
理解:验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
总结:真,继续;假,停止并报错。
9.指针的使用和传址调用
9.1strlen模拟实现
函数原型:size_t strlen ( const char * str );
size_t无符号整型 用%zd来打印
int my_strlen(char* str)//str接受一个字符串的起始地址
{
int count = 0;
assert(str);
//while (*str != '\0')
while(*str)
{
count++;
str++;//一个个向后遍历访问
}
return count;
}
int main()
{
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);//6
return 0;
}
9.2传值调用和传址调用
9.2.1例如:写⼀个函数,交换两个整型变量的值
可能的写法:
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
但是运行后发现,a和b的值并没有交换,为什么呢?
原来,Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这种叫传值调⽤。
结论:
- 实参传递给形参,形参有自己的独立空间
- 形参时实参的一份临时拷贝
- 对形参的修改,不会影响实参
9.2.2怎么办呢?
可以使用指针,将main函数中a,b的地址传递给Swap函数,就可以在Swap函数中通过地址间接访问main函数中的a和b了。
#include <stdio.h>
void Swap2(int*px, int*py)//通过指针间接访问a和b
{
int tmp = 0;//创建一个空杯子,实现交换两杯牛奶
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);//
printf("交换前:a=%d b=%d\n", a, b);
Swap2(&a, &b);//传地址给Swap函数
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
说明:这里调用Swap2函数的时候是把地址传递给了函数,这种函数调用方式叫:传址调用
总结:传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。
结语:好啦,以上就是小摆子今天带来的有关于指针的一些内容,水平有限,请大家多多包涵和支持,一键三连哦!