系列文章目录
C语言学习入门第六节————指针初阶(上)
指针初阶
- 指针是什么
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
一、指针是什么?
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量
1. 指针变量
- 可以通过 &(取地址操作符)取出变量的内存起始地址,可以把地址存放到一个变量中,这个变量就是指针变量
- 指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
#include <stdio.h>
int main()
{
int a = 100; //在内存中开辟一块空间
int* pa = &a; //取出变量a的地址,使用&(取地址操作符)
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址
//放在pa变量中,pa就是一个指针变量
// pa 是专门用来存放地址(指针)的,
// int * 可以分开理解
// * 告诉我们 pa 是 指针
// int 告诉我们 pa 指向的 a 是 int类型
printf("%p", pa);
return 0;
}
2. 内存单元的编址( * * * )
在32位机器上,地址是32个0或1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应是4个字节。
如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
二、指针和指针类型
指针的定义方式:
type + *
(如:int类型 – int * 变量)
不同 类型* 的指针是为了存放 该类型类型变量 的地址。
( 如:double* 类型 的指针是为了存放 double类型变量 的地址)
1.指针类型的意义(不同指针类型访问的对象的大小)( * * * )
对于指针变量: type * p;
- * :说明 p 是指针变量
- type:说明 p 指向的对象类型
- type:p 解引用的时候访问的对象的大小是 sizeof(type) --> 重点!
- 指针的类型决定了指针解引用时有多大的权限(能操作几个字节)
指针类型可以决定指针解引用的时候访问多少个字节。
int* 类型 的指针变量:
#include <stdio.h>
int main()
{
int a = 0x11223344; //0x开头的是16进制数字
int* pa = &a;
*pa = 0;
return 0;
}
char* 类型 的指针变量:( * * * )
#include <stdio.h>
int main()
{
int a = 0x11223344; //0x开头的是16进制数字
char* pa = &a;//pa能放下a的地址,因为char* pa也是指针,大小为四个字节
*pa = 0;
return 0;
}
2.指针 ± 整数( * * * )
指针的类型决定了指针向前或者向后走一步有多大(步长)
#include <stdio.h>
int main()
{
int a = 0x11223344; //0x开头的是16进制数字
int* pa = &a;
char* pc = &a;
printf("%p\n", pa);
printf("%p\n", pc);
//指针类型决定了指针+ / -整数时的步长
//整形指针+ / 整数,跳过四个字节
//字符指针+ / 整数,跳过一个字节。
printf("%p\n", pa+1);//跳过4个字节
printf("%p\n", pc+1);//跳过1个字节
return 0;
}
三、野指针
1. 野指针成因
(1). 指针未初始化
#include <stdio.h>
int main()
{
int* p;//局部变量不初始化的时候,内容是随机值
*p = 20;
printf("%d\n", *p);
return 0;
}
(2). 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;//相当于&arr[0];
int i = 0;
for (i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
(3). 指针指向的空间释放( * * * )
#include <stdio.h>
int* test()
{
int a = 110;
return &a;//a的空间再进入函数时创建,出函数被销毁(还给操作系统)
}
int main()
{
int* p = test();
printf("%d\n", *p);//这个时侯过来的地址赋给了p,此时p已经是野指针了
return 0;
}
2.如何规避野指针
指针初始化
(1). 明确知道这种应该初始化为谁的地址,就直接初始化
(2).不知道这种初始化为何值时,暂时初始化为NULL(空指针),没有指向任何有效的空间,该指针不能直接使用小心指针越界
指针指向的空间释放,及时将它置为NULL(空指针)
避免返回局部变量的指针
指针使用之前检查有效性
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
int* ptr = NULL;//ptr是一个空指针,没有指向任何有效的空间。这个指针不能直接使用
//int* ptr2;//野指针
if (ptr != NULL)
{
//ptr不为空指针时使用
}
return 0;
}
四、指针运算
- 指针 +/ - 整数
- 指针 - 指针
- 指针的关系运算
1.指针 +/ - 整数
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
//不使用下标访问数组
int* p = &arr[0]; //相当于arr
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]); //数组长度
//使用指针运算循环赋值:
//第一种方法:
for (i = 0; i < sz; i++)
{
*p = i;
p++; //指针运算
// p = p + 1; 指针加一,移到数组中的下一位
}
//第二种方法:
//for (i = 0; i < sz; i++)
//{
// *(p + i) = i;
//}z
z
//使用指针打印
p = arr; //地址位置还原到数组第一个元素位置进行打印
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
补充( * * * )
int a[13] = { 0 };
int* p = a;
*(p + i) == a[i]//p存放的是首元素地址
p == a//p和a都存放的是首元素地址
*(a + i) == a[i]//所以*(p + i)与 * (a + i)等价
//所以虽然我们在写代码时写的是a[i],但在本质上编译器在计算时也是按照*(a + 1) 计算的
a[i] == i[a] //[]只是个操作符,操作顺序可以调换
a[i] == *(a + i) == *(i + a) == i[a] //所以
2.指针 - 指针( * * * )
指针-指针 得到的数值的绝对值:是指针和指针之间的元素个数
指针-指针 运算的前提条件:指针和指针指向了同一块空间(同一数组)
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
// 指针-指针 得到的数值的绝对值:是指针和指针之间的元素个数
// 指针-指针 运算的前提条件:指针和指针指向了同一块空间(同一数组)
return 0;
}
使用 指针 - 指针 求字符串长度:
//应用:
#include <stdio.h>
//第一种版本:指针减指针求数组元素个数
int my_strlen(char* s)
{
char* start = s;
while (*s != '\0') //也可以写成 while(*s) ,\0的ASCII码值是0
{
s++;
} //循环到找到 \0 的地址
return s - start; //指针-指针:\0地址 - 首地址 = 元素个数
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
//第二种版本:正常思路
//#include <stdio.h>
//int my_strlen(char* s)
//{
// int count = 0; //统计字符串个数
// if (*s != '\0')//数组首元素不是结束标志
// {
// count++; //能进来说明有元素,个数++
// s++; //判断下一位
// }
// return count;
//}
//第三种版本:递归
//#include <stdio.h>
//int my_strlen(char* s)
//{
// if (*s == '\0')
// {
// return 0;
// }
// else
// {
// return 1 + my_strlen(s + 1);
// }
//}
3.指针的关系运算
地址是有大小的,指针的关系运算就是比较指针的大小。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
#include <stdio.h>
#define N_VALUES 5 //定义宏:数组长度为5
int main()
{
float values[N_VALUES]; //浮点数数组
float* vp;
for (vp = &values[N_VALUES]; vp > &values[0];)
// vp 等于 数组第五个元素的地址;vp 大于 数组第0个元素的地址
{
*--vp = 0; // vp先 -- ,再解引用 vp,把第四个元素赋值为0,然后是第三个、第二个……
}
//实现倒着给数组赋值
//代码简化,但是标准并不一定保证它可行
// for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
//{
*vp = 0;
//}
int i = 0;
for (i = 0; i < N_VALUES; i++)
{
printf("%d ", *(vp + i));
}
return 0;
}