前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言重点突破(2)指针(一)

C语言重点突破(2)指针(一)

作者头像
对编程一片赤诚的小吴
发布2024-01-23 15:21:32
1230
发布2024-01-23 15:21:32
举报

前言

指针对于C语言非常重要,因为它是C语言的重要特性之一。

指针可以帮助程序员更高效地处理内存,允许程序访问和修改内存中的数据。在C语言中,变量存储在内存中的某个位置上,变量的地址就是这个位置的地址,指针就是表示存储在某个内存位置上的变量地址的变量。

指针可以用于动态分配内存,使程序更加灵活。通过指针,程序员可以创建和操作数据结构,实现复杂的算法和数据处理程序。

除此之外,指针还可以用于定义函数参数和返回值。使用指针参数,函数可以修改调用者的变量,从而实现更加灵活的函数。指针返回值也可以帮助函数返回复杂的数据类型,例如数组和结构体。

因此,熟练掌握指针的用法是C语言程序员必备的基本技能,虽然非常重要,但对于指针的运用以及理解来说,在学习C语言的初期可是让不少程序员犯了难。

新手学习指针的难点有以下几个方面:

1. 理解指针的概念:指针是一个变量,它的值是另一个变量的地址。对于初学者来说,理解这一概念可能会比较困难,需要耐心学习和实践。

2. 理解指针与数组的关系:在C语言中,数组名本身就是一个指针,可以看作是数组第一个元素的地址。初学者需要理解指针与数组之间的关系,以便更好地理解和使用数组。

3. 理解指针与内存的关系:指针在C语言中常用于动态分配内存,并且可以访问和修改内存中的数据。初学者需要理解指针与内存之间的关系,了解如何使用指针来管理内存,避免内存泄漏和悬挂指针等问题。

4. 掌握指针的常用操作:初学者需要熟练掌握指针的常用操作,例如取地址运算符“&”和解引用运算符“*”,以及指针的类型转换、指针算术运算等。

5. 避免指针的常见错误:在使用指针时,初学者容易犯一些常见的错误,例如空指针、野指针、指针越界等。初学者需要了解这些错误的原因和解决方法,以避免程序出现不可预料的错误和问题。

本文重点

关于指针的概念及运用的重点太多,我们分成两部分进行讲解,本文将偏重解释指针的含义及最基本的运用

1. 指针是什么

如果只看名字的话,很容易就理解指针是什么,但这可是C语言里的知识,你该如何去理解呢?

假设你在图书馆里找一本书,但是书架上有很多书,你不知道具体位置。然后图书馆管理员告诉你:这本书在第三排第五本书的位置上。那么管理员给出的具体位置就相当于指针,它告诉你在哪里找到所需的信息。同样的,指针在编程语言中也是类似的作用,它指向某一块内存地址,让程序知道在哪里寻找需要的数据或者信息。

这是字节在内存中的存储方式,可以看到每一个字节都有对应的地址指向它,所以指针是内存中一个最小单元的编号,也就是地址。平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结一下就是:指针就是地址,口语中说的指针通常指的是指针变量。

下面我们来谈谈指针变量

我们可以通过&操作符(取地址操作符)将变量的内存地址取出来存放到指针变量里面。

代码语言:javascript
复制
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;
}

 总结:

指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。那这里的问题是:

一个小的单元到底是多大?(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根地址线,那能编址多大空间,自己计算。

这里我们就明白:

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以

一个指针变量的大小就应该是4个字节。

那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:

指针是用来存放地址的,地址是唯一标示一块地址空间的。指针的大小在32位平台是4个字节,在64位平台是8个字节.

2. 指针和指针类型

既然变量都有整型和浮点型这些类型,对于指针来说也是有对应的类型的。 

代码语言:javascript
复制
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;

不同的指针类型存放不同类型的内存地址,但这里有一个问题,既然都是放地址,那就是一串二进制代码,类型不同理论上不会影响存放,那为什么还要规定类型呢,多此一举?

我们来看看这段代码

代码语言:javascript
复制
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}

运行结果

 可以看到,char类型的指针加1跳过一个字节(char类型在内存中占一个字节),而int型指针加1跳过4个字节(int类型在内存中占4个字节),总结一下:

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

2.2指针的解引用

这里介绍一个操作符

解引用操作符在编程语言中通常用来访问指针所指向的内存地址中的值。在C++和类似的语言中,解引用操作符为 `*`,其作用是将指针变量前面加上 `*` 后,可以访问该指针所指向的内存地址中存储的值。例如:

代码语言:javascript
复制
//演示实例
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}

总结:

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

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

3. 野指针

所谓野指针,就是一个没有明确指向的指针,或者说这个指针指向的位置是未知的,那么就称这个指针为野指针,通常这种指针是不允许存在的,即使编译成功,也会存在一些危害:

1. 崩溃:当程序执行野指针时,由于指针指向无效或未知内存空间,程序会崩溃。

2. 内存泄漏:当程序使用野指针时,可能会导致内存泄漏,因为程序无法释放指向无效或未知内存空间的指针。

3. 数据损坏:如果野指针指向的内存空间在程序中被修改,可能会导致数据损坏或数据丢失。

4. 安全漏洞:攻击者可以利用野指针来执行恶意代码,从而获取操作系统或程序的控制权。

1.野指针的成因

1.定义指针时未进行初始化

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

2.指针越界访问

代码语言:javascript
复制
#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.指针指向的空间释放(这里涉及动态内存部分,不多赘述)。

2.如何规避野指针

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

4. 指针和数组

关于指针和数组的关系,我们先看看下面的代码

代码语言:javascript
复制
#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", &arr[0]);
return 0;
}

可以看到,&数组首元素下标和数组名的地址其实是一样的,可以说明数组名指向的就是数组的首元素地址,但有两种情况例外:

1.sizeof(数组名):它返回的是整个数组的元素大小,这里的数组名表示整个数组的地址;

2.&数组名:这里取出的是整个数组的地址,虽然它的值和数组首元素的地址是相同的,但含义不同(&数组名+1跳过整个数组,数组名+1跳过的是一个元素)。

既然数组名是首元素的地址,我们可以把它存到指针中,通过指针来访问整个数组

代码语言:javascript
复制
#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(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}

 所以p+i就是数组下标为i的地址

代码语言:javascript
复制
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;
}

5. 二级指针

前面说过,我们所谓的指针就是指一种存放变量地址的变量,叫指针变量,既然是变量,它应该也是有地址的,存放变量的地址用一级指针,那存放一级指针的地址,自然就叫做二级指针。

对于二级指针的运算,通常有一下几种

*ppa相当于对二级指针ppa解引用,得到pa,将b的地址赋给pa;

代码语言:javascript
复制
int b = 20;
*ppa = &b;//等价于 pa = &b;

**ppa相当于先对ppa解引用得到pa,在对pa解引用拿到a的值,将30赋给a。 

代码语言:javascript
复制
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
int arr1[5];
char arr2[6];

6. 指针数组

指针数组,到底属于指针类型还是数组类型呢?这个很好区分

存放整型元素的,我们叫做整型数组;

存放字符类型的,我们叫做字符数组,

举一反三,指针数组存放的类型自然就是指针,是属于数组。同时也有存放不同类型指针的数组,如整型指针,字符型指针等等。

有了指针数组,我们就可以简单模拟创建一个二维数组。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 本文重点
    • 1. 指针是什么
      • 2. 指针和指针类型
        • 2.2指针的解引用
      • 3. 野指针
        • 1.野指针的成因
        • 2.如何规避野指针
      • 4. 指针和数组
        • 5. 二级指针
          • 6. 指针数组
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档