0
点赞
收藏
分享

微信扫一扫

【C语言】指针---初阶

目录

一、指针是什么?

1.1指针与内存间的关系

内存

1.2指针变量存放地址

二、指针和指针类型

2.1指针有哪些类型?

2.2指针类型的意义是什么?

2.2.1指针的类型决定了指针向前或者向后走一步有多大(距离)。

2.2.2 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

 三、野指针

3.1野指针成因

3.1.1. 指针未初始化

3.1.2. 指针越界访问 

3.1.3. 指针指向的空间释放 

3.2 如何规避野指针

四、指针运算

4.1 指针+-整数

4.2 指针-指针

4.3 指针的关系运算

五、 指针和数组

六、二级指针

6.1二级指针与多级指针 

6.2二级指针运算 

七、指针数组


一、指针是什么?

1.1指针与内存间的关系

这里我们知道char类型占1个字节的内存空间,short类型占2个字节的内存空间,int类型占4个字节的内存空间,long类型占4个字节的内存空间,long long类型占8个字节的内存空间,float类型占4个字节的内存空间,double类型占8个字节的内存空间。

内存

这里我们就明白:
🌴 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储(因为一个字节等于八个比特位,而一个0或1占一个比特位),所以一个指针变量的大小就应该是4个字节。
🌴同理,那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

1.2指针变量存放地址

注意:这里a的值以16进制的形式存储在内存中的并且是倒着存放的(这里涉及到大小端问题) 。

总结:指针变量是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。

二、指针和指针类型

2.1指针有哪些类型?

char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;

不同类型的指针其实是为了存放对应类型变量的地址。 

2.2指针类型的意义是什么?

2.2.1指针的类型决定了指针向前或者向后走一步有多大(距离)。

这里我们不难看出,char*类型的指针加一时,它的地址增加了1个字节,而int*类型的指针加一时,它的地址增加了4个字节 。

2.2.2 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

📌char*指针

这里是将int类型的变量n的地址强制转换赋给了char类型指针pc 

指针pc解引用后:

📌int*指针 

指针pi解引用后:

 

 三、野指针

3.1野指针成因

3.1.1. 指针未初始化

#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}

3.1.2. 指针越界访问 

#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}

3.1.3. 指针指向的空间释放 

动态开辟一块空间时返回的值存放到一个指针中,当使用完这个指针后没有释放时,该指针就会变为野指针。

3.2 如何规避野指针

1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性

#include <stdio.h>
int main()
{
int *p = NULL;

int a = 10;
p =
//检查指针的有效性
if(p != NULL)
{
*p = 20;
}
return 0;
}

四、指针运算

4.1 指针+-整数

指针加+-运算用于将指针移动指定的偏移量,以便访问其他地址处的数据。例如,如果有一个指向整型数组的指针p,可以使用p+n或p-n将指针移动n个整型长度的位置。

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = vp < &values[N_VALUES];)
{
*vp++ = 0;
}

4.2 指针-指针

可以对两个指针进行减法运算,得到它们之间的距离(以元素个数为单位)。这在处理数组或动态内存分配时非常有用。例如,假设有两个int类型的指针ptr1和ptr2,我们可以进行如下操作:

int distance = ptr2 - ptr1; // 计算ptr2和ptr1之间的距离(以int元素个数为单位)

需要注意的是,指针运算应该谨慎使用,确保指针指向的内存是有效的。否则,可能会导致未定义行为或内存错误。此外,不同类型的指针之间不应进行直接算术运算,因为它们可能引用不同大小的数据。只有在指向同一数组的元素或有效分配的内存区域时,指针之间的运算才是有效的。 

4.3 指针的关系运算

在C、C++等编程语言中,指针的关系运算用于比较指针的地址或者判断指针是否为NULL。下面是指针的关系运算符及其含义:

📌相等(==):用于判断两个指针是否指向同一个内存地址。如果两个指针指向相同的地址,则关系表达式为真,否则为假。

int* ptr1;
int* ptr2;

if (ptr1 == ptr2) {
// 指针ptr1和ptr2指向相同的地址
}

📌不相等(!=):用于判断两个指针是否指向不同的内存地址。如果两个指针指向不同的地址,则关系表达式为真,否则为假。

int* ptr1;
int* ptr2;

if (ptr1 != ptr2) {
// 指针ptr1和ptr2指向不同的地址
}

📌大于(>)、小于(<)、大于等于(>=)、小于等于(<=):这些关系运算符用于比较两个指针所指向的地址之间的顺序关系。只有当指针指向同一个数组中的元素或者在有效的内存范围内时,这些关系运算符才有定义。

int* ptr1;
int* ptr2;

if (ptr1 > ptr2) {
// ptr1指向的地址在ptr2指向的地址之后
}

if (ptr1 < ptr2) {
// ptr1指向的地址在ptr2指向的地址之前
}

if (ptr1 >= ptr2) {
// ptr1指向的地址在ptr2指向的地址之后或相同
}

if (ptr1 <= ptr2) {
// ptr1指向的地址在ptr2指向的地址之前或相同
}

📌空指针检查:指针的关系运算还可以用于检查指针是否为空(指向NULL)。在C和C++中,NULL是一个预定义的宏,用于表示一个空指针。

int* ptr = NULL;

if (ptr == NULL) {
// 指针ptr是空指针
}

if (ptr != NULL) {
// 指针ptr不是空指针
}

需要注意的是,对于关系运算符(>, <, >=, <=),只有在指针指向同一数组的元素或有效分配的内存区域时,比较的结果才是有效的。在进行指针比较之前,最好确保指针指向的内存是有效的,否则可能会导致未定义行为。

五、 指针和数组

我们来看一个例子:

#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n",
return 0;
}

运行结果: 

可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(一般情况下)
那么这样写代码是可行的:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:

#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}

运行结果: 

所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
如下:

#include<stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}

运行结果: 

 

六、二级指针

二级指针(double pointer)是指指向指针的指针。我们可以使用指向指针的指针来处理指针的引用或修改。它们在一些情况下非常有用,特别是在涉及到函数参数传递和动态内存分配等方面。

定义二级指针的语法为在指针类型名称前加上两个星号(**):

int** doublePtr;

6.1二级指针与多级指针 

#include<stdio.h>
int main()
{
int a = 10;
int* pa =
int** ppa =
int*** pppa =
printf("%p\n", a);
printf("%p\n",

printf("\n%p\n", pa);
printf("%p\n",

printf("\n%p\n", ppa);
printf("%p\n",

printf("\n%p\n", pppa);
printf("%p\n",
return 0;
}

 

6.2二级指针运算 

📌*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .

int b = 20;
*ppa = //等价于 pa =

📌**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

七、指针数组

指针数组是一个数组,其中的每个元素都是指针。我们可以创建指针数组来存储一组指向不同数据类型的指针。这些指针可以指向数组、字符串、函数等各种数据类型,从而实现更灵活的数据结构和操作。

定义指针数组的语法为在指针类型名称后加上方括号([]):

int* ptrArray[5]; // 定义一个包含5int指针的数组

 

在上述示例中,ptrArray是一个包含5个指向int类型数据的指针的数组。我们可以通过数组下表来访问和操作这些指针:

int num1 = 10;
int num2 = 20;
int num3 = 30;

int* ptrArray[3]; // 定义一个包含3int指针的数组

ptrArray[0] = // 第一个指针指向num1的地址
ptrArray[1] = // 第二个指针指向num2的地址
ptrArray[2] = // 第三个指针指向num3的地址

printf("Value at index 0: %d\n", *ptrArray[0]); // 输出:Value at index 0: 10
printf("Value at index 1: %d\n", *ptrArray[1]); // 输出:Value at index 1: 20
printf("Value at index 2: %d\n", *ptrArray[2]); // 输出:Value at index 2: 30

指针数组非常适用于存储字符串数组。在C语言中,字符串被表示为字符数组,并且每个字符串都以空字符('\0')结尾。使用指针数组可以更方便地管理字符串的集合:

const char* names[] = { "Alice", "Bob", "Charlie" };

在上面的示例中,我们创建了一个包含3个指针的指针数组,每个指针都指向一个字符串常量。

需要注意的是,指针数组中的每个指针应该指向有效的内存地址。否则,当我们尝试通过这些指针访问数据时,可能会导致未定义行为或者出现野指针的问题。确保在使用指针数组之前,为每个指针分配合适的内存或者让它们指向有效的数据。同时,记得在不再需要这些指针数组时,及时释放其所指向的内存,避免内存泄漏。

🔥今天的分享就到这里,如果觉得博主的文章还不错的话,请👍三连支持一下博主哦🤞

 

举报

相关推荐

0 条评论