指针是什么?
指针理解的2个要点:
1.指针是内存中一个最小单元的编号,也就是地址
2.平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
最小的内存单元,也就是一个地址的空间是一个字节。
指针变量
我们可以通过&(取地址操作符)取出变量的内存地址,把地址可以存放到一个变量中,这个变量就是指针变量。
#include
int main()
{
int a = 10;
int* pa = &a;
printf("%p", pa);
return 0;
}
//--------------------
//编译器运行结果为
//00F3FA6C
a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在pa变量中,pa就是一个之指针变量。
pa中存放的是a变量的地址,同样pa也有自己所对应的地址。
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
一个小的单元到底是多大?(1个字节)
如何编址?
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte ==2^32/1024KB==2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB)
4G的空间进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址(2^64==2^32*2^32==2^32*4GB)
2的32次方个4GB空间,
这里我们就明白:
总结:
当想通过地址进行访问的时候使用解引用操作符(间接访问操作符)
#include
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
printf("%d",a);
return 0;
}
//--------------------
//编译器运行结果为
//20
#include
int main()
{
int* pa;
char* pc;
float* pf;
printf("%d\n", sizeof(pa));
printf("%d\n", sizeof(pc));
printf("%d\n", sizeof(pf));
return 0;
}
//------------------
//编译器运行结果为
//4
//4
//4
char* 类型的指针是为了存放 char 类型变量的地址。
float *类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
既然不同类型的指针变量大小相同,为何不只创建一个类型?
那指针类型的意义是什么?
#include
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
#include
int main()
{
int a = 0x11223344;
char* pa = &a;
*pa = 0;
return 0;
}
上述两个代码都能存储a的地址,但是进行解引用操作的时候会有不同。
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
double*的解引用能访问八个字节。
#include
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("pa=%p\n", pa);
printf("pc=%p\n", pc);
printf("pa+1=%p\n", pa + 1);
printf("pc+1=%p\n", pc+1);
return 0;
}
//--------------------
//编译器运行结果为
//pa = 012FFC40
//pc = 012FFC40
//pa + 1 = 012FFC44
//pc + 1 = 012FFC41
**总结:**指针的类型决定了指针向前或者向后走一步有多大距离(步长)。
int*指针+1,意思是跳过一个整型,也就是向后走4个字节。
char*指针+1,意思是跳过1个字节.
double*指针+1,意思是跳过8个字节。
short*指针+1,意思是跳过2个字节。
应用
存储1-10在数组中
方法一:
#include
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*pa = i + 1;
pa++;
}
return 0;
}
方法二:
#include
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(pa+i) = i + 1;
}
return 0;
}
因为知道int*
类型的步长,所以++以后对应的地址正好是下一个空间的地址。如果用char*
只能访问10个字节。
概念: 野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的)。
1.指针未初始化
#include
int main()
{
int* p; //野指针
*p = 20;
return 0;
}
由于指针没有初始化,所以p存放的是随机地址,这是p就是野指针。
2.指针越界访问
#include
int main()
{
int arr[10] = { 0 };
int i = 0;
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i <= sz; i++)
{
*p = i;
p++;
}
return 0;
}
当指针指向的范围超出数组arr的范围时,p就是野指针。
3.指针指向的空间释放
#include
int* test()
{
int num = 100;
return #
}
int main()
{
int* p =test();
*p = 200;
return 0;
}
虽然返回了num的地址,但是num的空间已经被释放,再通过p访问**他人空间(原num空间)**时,p就是野指针。
1.指针初始化
2.小心指针越界
3.指针指向空间释放,及时置为NULL(空指针)
4.避免返回局部变量的地址
5.指针使用之前检查有效性
1.指针初始化
#include
int main()
{
int a = 10;
int* pa = &a; //明确初始化
//NULL--本质是0,是用于初始化指针的
int* p = NULL;
return 0;
}
NULL–空指针–本质是0,是用于初始化指针的。
2.小心指针越界
#include
int main()
{
int arr[10] = { 0 };
int i = 0;
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i <= sz; i++)
{
*p = i;
p++;
}
return 0;
}
3.指针指向空间释放,及时置为NULL
这里涉及到动态内存分配问题,仅简单介绍使用。
动态内存分配
//申请空间
int* p = malloc(40);
//使用空间
//使用后释放空间
free(p);//释放空间
p = NULL;//将p置为空指针,防止成为野指针。
4.避免返回局部变量的地址
#include
int* test()
{
int num = 100;
return #
}
int main()
{
int* p =test();
*p = 200;
return 0;
}
由于局部变量存放在栈区,所以也叫作避免返回栈空间的地址。
5.指针使用之前检查有效性
对于空指针是不能进行访问。
#include
int main()
{
int* p = NULL;
printf("%d\n", *p);
return 0;
}
空指针进行解引用,也就是把0作为地址进行访问,这个时候程序会崩掉。
#include
int main()
{
int* p = NULL;
if (p != NULL)
{
printf("%d\n", *p);
}
return 0;
}
#include
int main()
{
int a = 10;
int* p = &a;
printf("%d\n", *p);
return 0;
}
这是指针初阶的前半章节语法,至于初阶指针剩下的内容,我们在下一篇文章当中再进行介绍。