首页 > 其他分享 >C语言指针统览

C语言指针统览

时间:2023-01-11 19:31:08浏览次数:120  
标签:变量 int void C语言 地址 数组 统览 指针

前言

本文对C语言指针指针使用时的问题做一个概览性的总结,并对一些值得探讨的问题进行讨论。阅读本文,读者能达到统览C语言指针的目的。以下的讨论只针对32/64位机器。


指针纲领:

C语言指针统览_内存编址


什么是指针

要知道什么是指针,就要先了解内存的编址方法。

内存的编址

存储器由一块块的空间(存储单元)组成,为了方便寻找到每一块空间,我们需要对每一个空间进行标识——内存编址。字节(Byte)是讨论内存空间时的基本单位,每个存储单元的大小是一个字节

对内存进行编址,使得每个内存中的每个字节都有一个特定的编号,这个编号就是该内存单元的地址

C语言指针统览_野指针_02


指针和指针变量

指针就是内存的地址。指针变量是存储指针的变量。平常说的指针通常指的是指针变量

32位机器上,地址是32个二进制位(bit)组成的二进制序列,需要用4个字节进行存储,所以指针变量的大小是4个字节;同理,在64位机器上,指针变量的大小是8个字节


二级指针和多级指针

取地址操作符(&)可以拿到一个变量的地址,进而可以将其放到一个指针变量中。指针变量是个变量,本身占用内存空间,并通过存储内存地址另外指向一块内存空间。如果对指针变量进行取地址操作,取出的便是指针变量的地址,若用一个变量存储这个地址,则这个变量便是二级指针变量。通过这个二级指针可以拿到其指向的一级指针。

n级指针变量用来存储(n - 1)级指针变量的地址,这个指针变量就是一个多级指针

int main()
{
int a = 10;
int* pa = &a;//pa是一个一级指针
int** ppa = &pa;//ppa是一个二级指针
**ppa = 20;//通过二级指针操作数据
return 0;
}

指针类型

作为一个变量,指针具有多种类型。

整型指针

指向一个整型空间的指针变量就是整型指针。

需要注意的是,整型变量具有4个字节,而一个指针只能标识一个字节的空间,所以整型指针指向的仅仅是整型变量的第一个字节,对指针进行解引用时,编译器会自行向后取四个字节以完整地拿到这个整型。

void test02(void)
{
int a = 10;
int* pa = &a;
}

C语言指针统览_转移表_03


字符指针

指向一个字符空间的指针变量就是字符指针。

需要注意的是常量字符串的存储方式,用一个字符指针存储常量字符串时,字符指针存储的仅是第一个字符的地址,且该地址指向的内容不可被修改(常量字符串不能被修改),最好用​​const​​对该指针进行修饰。

void test03(void)
{
const char* p = "hello world!";
}

C语言指针统览_回调函数_04

数组指针

指向一个数组的指针变量就是数组指针。对数组指针进行解引用,拿到的是该数组的数组名


函数指针

函数的地址

函数具有地址。函数在被准备调用时会在栈区创建函数栈帧,为函数和函数参数创建临时空间,当一切准备工作就绪时,某地址处的函数被调用。

存储函数地址的指针变量就是函数指针。通过函数指针可以找到指向的函数,进而可以调用该函数。

int Add(int x, int y)
{
int sum = x + y;
return sum;
}

void test04(void)
{
int a = 10;
int b = 20;
int ret = Add(a, b);
int(*pf)(int, int) = &Add;//pf是一个函数指针
pf(3, 4);//通过函数指针调用函数、
(*pf)(3, 4);与第13行代码效果相同,*号无实际意义
}

C语言指针统览_回调函数_05


回调函数

回调函数是通过函数指针被调用的函数。如果将一个函数指针作为参数传递给另外一个函数,当函数通过这个函数指针调用指向的函数时,被调用的这个函数就是一个回调函数。回调函数不是实现方直接调用的,而是特定条件或事件发生时由另一方调用的。

通过回调函数,我们可以设计一个可以排序多种数据类型的冒泡排序

void Swap(char* e1, char* e2, size_t width)
{
//逐字节交换
for (size_t i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}


//改造冒泡排序
void BubbleSort(void* base, size_t num, size_t width, int (*cmp_fun)(const void*, const void*))
{
for (size_t i = 0; i < num - 1; i++)
{
for (size_t j = 0; j < num - 1 - i; j++)
{
if (cmp_fun((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}


int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}


void test1(void)
{
int arr[] = { 1,3,5,7,2,4,6,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
BubbleSort(arr, sz, sizeof(int), cmp_int);//回调函数
}


struct Demo
{
int n;
char arr[10];
};


int cmp_str(const void* e1, const void* e2)
{
return strcmp(((struct Demo*)e1)->arr, ((struct Demo*)e2)->arr);
}


void test2(void)
{
struct Demo d[3] = { {2, "zeze"}, {3, "ahah"}, {1, "hehe"} };
BubbleSort(d, 3, sizeof(d[0]), cmp_str);
}

int main()
{
//test1();
test2();
return 0;
}

其中​​cmp_​​就是一个回调函数。


指针类型的意义

指针类型决定了指针与整数运算时,指针移动的步长

指针类型决定了对指针进行解引用时访问空间的大小


野指针

是什么

“野指针”就是指向位置不可知(随机的、不正确的、没有明确限制的)的指针。野指针是造成内存错误使用和管理的重要原因。


野指针的成因

指针未初始化。当定义一个指针变量时,其指向的内容是随机的,若直接使用就会造成问题。

指针越界访问。这个问题常见于数组操作中。操作数组时,若不注意数组的大小和范围,就会造成指针越界。

指针指向的空间被释放。返回指向栈区空间的指针,或者​​free​​空间后仍使用该指针,就会造成问题。


如何规避野指针


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

关于更多野指针的讨论,可以参考我之前的一篇关于​​内存管理​​的文章。


指针运算

作为一种变量,指针和整数、指针和指针之间都可以进行运算。两种运算分别有不同的意义。

指针和整数的运算

指针+-整数运算可以实现指针的移动,移动的步长取决于指针的类型。


指针和指针的运算

指向同一块空间的两指针的减法运算的结果的绝对值是两指针之间的元素数目。指针之间的加法运算没有实际意义。

strlen()函数的实现:

size_t my_strlen(const char* p)
{
assert(p);
const char* end = p;
while (*end++);
return end - p - 1;//指针运算
}


指针的关系运算

指针之间可以进行关系运算。指向同一块空间的指针的关系运算常用于控制一些操作的开始或终止

需要注意的时,ANSI C规定,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}//不建议这样写

//规范写法:
for(vp = &values[0]; vp <= &values[N_VALUES-1]; vp++)
{
*vp = 0;
}


指针和数组

对二维数组的理解

二维数组的存储空间是连续的,可以将二维数组看做存储数个一维数组的数组,二维数组的每行的元素是一维数组的数组名。二维数组的数组名是第一行的一维数组的地址,即一个数组指针

C语言指针统览_内存编址_06


指针和数组的联系

数组名是数组的首元素地址;对数组名进行取地址操作,取出的是整个数组的地址,需要用一个数组指针存储。这样我们就可以理解为什么二维数组的数组名是一个数组指针:二维数组的数组名是二维数组首元素的地址,而通过对二维数组的理解可以得知,二维数组的首元素其实是第一行的数组名,对数组名进行取地址操作,取出的是第一行的地址,所以拿到二维数组的数组名就拿到了二维数组第一行的地址

另外,用​​sizeof(ArrayName)​​计算数组大小时,这里的数组名代表的同样是整个数组


指针数组

指针数组是存放指针的数组

数组传参

数组传参时,函数可以用一个数组接收参数,也可以用一个指针接收参数,但是数组本质上传递的是一个指针,该指针保存了数组首元素的地址。要注意选择合适的指针类型接收数组参数。

void Func(int arr[ROW][COL], int row, int col)
{
//用数组接收参数
;
}

void Func(int(*parr)[COL], int row, int col)
{
//用指针接收参数
;
}

指针应用

转移表

函数指针数组常被用作转移表(jump table)。使用转移表可以减少代码冗余,增加代码可读性,是一种良好的设计方案。转移表中的函数必须是同类的函数

简易计算器:

int add(int a, int b){
return a + b;
}

int sub(int a, int b){
return a - b;
}

int mul(int a, int b){
return a * b;
}

int div(int a, int b){
return a / b;
}

int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };//转移表
while (input)
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
{
printf("输入有误\n");
}
printf("ret = %d\n", ret);
}
}


基本数据结构的实现

在C语言中,线性表和二叉树的实现离不开指针,指针将各个结构的各个节点连接起来,进而保证结构的完整性。更高级的数据结构的实现都是建立在此基础上的。


内存管理

C语言内存管理必须要有指针,C程序员通过指针对内存进行布局和使用,离开了指针,程序员面对内存就会手足无措。

作为一把无所不能的菜刀,使用指针管理内存时往往会出现一些问题,常见的​​内存管理​​问题和规避方法可以参考我之前的一篇文章。




标签:变量,int,void,C语言,地址,数组,统览,指针
From: https://blog.51cto.com/u_15752114/6002472

相关文章

  • 算法入门(第二天)---双指针977,189
    977.有序数组的平方给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。输入:nums=[-4,-1,0,3,10]输出:[0,1,......
  • c语言实现三子棋
    前言:在此之前我们学习了循环,函数,数组等相关知识,我们来写一个小游戏练练手概述:代码大致分为三部分程序主函数,函数,声明函数(这一点我们在通讯录项目是就介绍过了,将代码分为三部......
  • 哪些软件和程序用了C语言?
    在日常生活中,很多系统软件和桌面应用程序都采用C语言进行开发,下面给出了一些示例。1.操作系统UNIX是第一个使用**语言设计的操作系统,它使用的编程语言就是C语言。后来,Mic......
  • 一个C语言的剪刀石头布小游戏
    /******************************************************石头剪刀布的程序geek_monkey于2015年3月3日修改了bug(输入字符非石头剪刀布都算是玩家赢)编译环境为VC+......
  • 指针知识点总结
    指针总结基础概念系统给虚拟内存的每个存储单元分配了一个编号,0x00000000-0xffffffff,这个编号是地址,指针就是地址内存数据的访问方式:(1)直接访问—按变量名存取变量......
  • c++ 常量指针和指针常量
    常量指针:const在*之前指针的地址是可以被再次赋值的(可以修改的)指针地址上面的值(变量)是不能被修改的常量指针的常量是不能被改变的指针常量:const在*之后指针的地......
  • C语言学生成绩录入系统
    C语言学生成绩录入系统学生成绩录入系统录入10名学生的学号,姓名,及3门课程(高数、马克思、C语言)的平时成绩与考试成绩,3门课程的比例如下:高数:总成绩=平时成绩30%+考试成......
  • C语言学生成绩管理系统[2023-01-10]
    C语言学生成绩管理系统[2023-01-10]学生成绩管理系统建立学生结构体类型,包括:学号(学号11位)、姓名、3门课成绩及总分。采用链表来存放学生信息,从键盘录入10个同学的信息(总......
  • cpp之智能指针
    1.介绍本文介绍智能指针的使用。智能指针是c++中管理资源的一种方式,用智能指针管理资源,不必担心资源泄露,将c++程序员从指针和内存管理中解脱出来,再者,这也是c++发展的趋......
  • 5. C语言scanf gets输入字符串的区别
    1.scanf这种格式串不能接收带空格的字符串,比如输入"abc123678"的话,字符串只会接收到abc,遇空格就结束2. 使用 gets() 时,系统会将最后“敲”(Enter)的换行符从缓冲区中......