当前位置: 首页 > article >正文

指针(四)

因为前期在学驱动,所以花了一天时间借鉴了别的资料,把本科学的C语言捡起来。

指针的基本概念

堆栈有栈顶指针,队列有头指针和尾指针,这些概念中的"指针"本质上是一个整数,是数组的索引,通过指针访问数组中的某个元素,经过学习我们在间接寻址那里看到了另一个指针的概念,把一个变量所在的内存单元的地址保存在另外一个内存单元中,保存地址的这个内存单元称为指针,通过指针和间接寻址访问变量,这种指针在C语言中可以用一个指针类型的变量表示,例如某程序中定义了以下全局变量:

int *pi=&i;表示定义一个指向int型的指针变量pi,并用i的地址来初始化pi.

int i;
int *pi = &i;
char c;
char *pc = &c;

在这里插入图片描述
注意:
后面两行代码定义了一个字符型变量c和一个指向c的字符型指针pc,注意pi和pc虽然是不同类型的指针变量,但它们的内存单元都占4个字节,因为要保存32位的虚拟地址,同理,在64位平台上指针变量都占8个字节。

pi是int型的,pc是char型的,pi=pc;这样赋值就是错误的。但是可以先强制类型转换然后赋值:

pi = (int *)pc;

在这里插入图片描述
但是:
**
现在pi指向的地址和pc一样,但是通过pc只能访问到一个字节,而通过pi可以访问到4个字节,后3个字节已经不属于变量c了,除非你很确定变量c的一个字节和后面3个字节组合而成的int值是有意义的,否则就不应该给pi这么赋值。因此使用指针要特别小心,很容易将指针指向错误的地址。
**
PI的地址和PC的地址一样了,这样就导致了可以访问的只有一个字节的地址了。原来是4字节。

为什么会出现野指针?

因为堆栈上分配变量的地址是随机的,也就是指针变量P所指向的内存地址也是不确定的。所以这种指向不确定地址的指针成为 “ 野指针 ”,为了避免野指针,在定义指针变量时就应该给它明确的初值,或者把它初始化位NULL:

int main(void)
{
 int *p = NULL;
 ...
 *p = 0;
 ...
}

空指针

把地址0转换成指针类型。
它的特殊之处在于,操作系统不会把任何数据保存在地址0及其附近,也不会把地址0-0xfff的页面映射到物理内存,所以任何对地址0的访问都会立刻导致段出错。*p=0;会导致段错误,就像放在眼前的炸弹一样很容易被找到。

void* 指针

ANSI在将C语言标准化时引入了void类型,void指针与其他类型的指针之间可以隐式转换,而不必用类型转换运算符。注意,只能定义void指针,而不能定义void型的变量,因为void指针和别的指针一样都占4个字节,而如果定义void型变量,编译器不知道该分配几个字节给变量。

void func(void *pv)
{
 /* *pv = 'A' is illegal */
 char *pchar = pv;
 *pchar = 'A';
}
int main(void)
{
 char c;
 func(&c);
 printf("%c\n", c);

指针类型的参数和返回值

#include <stdio.h>
int *swap(int *px, int *py)
{
    int temp;
    temp = *px;
    *px = *py;
    *py = temp;
     return px;
}
int main(void)
{
    int i = 10, j = 20;
    int *p = swap(&i, &j);
    printf("now i=%d j=%d *p=%d\n", i, j, *p);
    return 0;
}

通过地址访问到内部变量,将内部变量进行交换。

指针和数组

int a[10];
int *pa = &a[0];
pa++;

在这里插入图片描述
在函数原型中,如果参数是数组,则等价于参数是指针的形式,例如:

void func(int a[10])
{
 ...
}

等价于

void func(int *a)
{
 ...
}

等价于

void func(int a[])
{
 ...
}

指针与const 限定符

  1. 如下
const int *a;
int const *a;

a是一个指向const int型的指针,a所指向的内存中的内容是不可改变的,所以(*a)++是不允许的,但a可以改写,所以a++是允许的。
2. 如下

int * const a;

a是一个指向int型的const指针,*a是可以改写的,但a不允许改写。
3.

int const* const a;

a是一个指向const int型的const指针,因此*a和a都不允许改写。
指向非const变量的指针或者非const变量的地址可以传给指向const变量的指针,编译器可以做隐式类型转换,例如:

char c = 'a';
const char *pc = &c;

但是,指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针,以免投过后者意外改写了前者所指向的内存单元,例如对下面的代码编译器会报警告:

const char c = 'a';
char *pc = &c;

即使不用const限定符也能写出正确的程序,但良好的编程习惯应该尽可能多的使用const,因为:

1.const给读代码的人传达非常有用的信息。比如一个函数的参数是const char *,你在调用这个函数时就可以放心地传给它char *或const char *指针,而不必担心指针所指的内存单元被改写。

2.尽可能多地使用const限定符,把不该变的都声明成只读,这样可以依靠编译器检查程序中的Bug,防止意外改写数据。

3.const对编译器优化是一个有用的提示,编译器也许会把const变量优化成常量。

指针与结构体

首先定义一个结构体类型,然后定义这种类型的变量和指针:

struct unit {
 char c;
 int num;
};
struct unit u;
struct unit *p = &u;

要通过指针p访问结构体成员可以写成(*p).c和(*p).num,为了书写方便,C语言提供了->运算符,可以写成p->c和p->num。

指向指针的指针与指针数组

也就是二级指针:

int i;
int *pi = &i;
int **ppi = &pi;

我们知道main函数的标准原型应该是int main(int argc,char *argv[]);argc是命令行参数的个数,而argv是一个指向指针的指针,为什么不是指针数组?因为前面讲过,函数原型中的[]表示指针而不表示数组,等价于char **argv.那为什么要写成char *argv[]而不写成char **argv?这样写给读代码的人提供了有用的信息,argv不是指向单个指针,而是指向一个指针数组的首元素。数组中每个元素都是char *指针,指向一个命令行参数字符串。

打开命令行参数

#include <stdio.h>
int main(int argc, char *argv[])
{
 int i;
 for(i = 0; i < argc; i++)
 printf("argv[%d]=%s\n", i, argv[i]);
 return 0;
}

编译:

$ gcc main.c
$ ./a.out a b c
argv[0]=./a.out
argv[1]=a
argv[2]=b
argv[3]=c
$ ln -s a.out printargv
$ ./printargv d e 
argv[0]=./printargv
argv[1]=d
argv[2]=e

在这里插入图片描述由于argv[4]是NULL,我们也可以这样循环遍历argv:

for(i=0; argv[i] != NULL; i++)

NULL标识着argv的结尾,这个循环碰到NULL就结束,因而不会访问越界,这种用法很形象地称为Sentinel,NULL就像一个哨兵守卫着数组的边界。

指向数组的指针与多维数组

指向数字的指针其实前面讲过的数组指针。

int (*a)[10];

int (*a)[10]可以拆分成

typedef int t[10];
t *a;

t代表由10个int组成的数组类型,a则是指向这种类型的指针。
也就是:

int a[10];
int (*pa)[10] = &a;

a是一个数组,在&a这个表达式中,数组名做左值,取整个数组的首地址赋给指针pa。注意,&a[0]表示数组a的首元素的首地址,而&a表示数组a的首地址,显然这两个地址的数值是相同的,但这两个表达式的类型是两种不同的指针类型,前者的类型是int ,而后者的类型是int()[10]。

函数类型和函数指针类型

在C语言中,函数也是一种类型,可以定义指向函数的指针。我们知道,指针变量的内存单元存放一个地址值,而函数指针存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子:

函数指针:

#include <stdio.h>
void say_hello(const char *str)
{
 printf("Hello %s\n", str);
}
int main(void)
{
 void (*f)(const char *) = say_hello;
 f("Guys");
 return 0;
}

分析一下变量f的类型声明void(*f)(const char ),f首先跟号结合在一起,因此是一个指针。(*f)外面是一个函数原型的格式,参数是const char *,返回值是void,所以f是指向这种函数的指针。而say_hello的参数是const char *,返回值是void,正好是这种函数,因此f可以指向say_hello。注意,say_hello是一种函数类型,**而函数类型和数组类型类似,做右值使用时自动转换成函数指针类型,所以可以直接赋给f,当然也可以写成void (*f)(const char *) =&say_hello;**把函数say_hello先取地址再赋给f,就不需要自动类型转换了。

可以直接通过函数指针调用函数,如上面的f(“Guys”),也可以先用*f取出它所指的函数类型,再调用函数,即(*f)(“Guys”)。可以这么理解:函数调用运算符()要求操作数是函数指针,所以f(“Guys”)是最直接的写法,而say_hello(“Guys”)或(*f)(“Guys”)则是把函数类型自动转换成函数指针然后做函数调用。

文章来源:
https://blog.csdn.net/m0_58367586/article/details/128612310


http://www.kler.cn/a/160828.html

相关文章:

  • Java 责任链模式 减少 if else 实战案例
  • 前端,location.reload刷新页面
  • js 获取某日期到现在的时长 js 数字补齐2位
  • Unity学习笔记(4):人物和基本组件
  • 半导体企业如何利用 Jira 应对复杂商业变局?
  • 灰狼优化算法
  • 2.面向对象编程风格
  • 【NLP】如何管理大型语言模型 (LLM)
  • 常规外网打点拿下域控
  • 鸿蒙一出,android开发处境再受重创
  • Kubernetes权威指南:从Docker到Kubernetes实践全接触(第5版)读书笔记 目录
  • screen 常用命令
  • 小程序商城制作一个需要多少钱
  • C++指针作业
  • RT-DETR手把手教程:NEU-DET钢材表面缺陷检测任务 | 不同网络位置加入EMA注意力进行魔改
  • Python-文件详解
  • 烈酒行业分析:预计2029年将达到17628亿元
  • Qt6 QRibbon 一键美化Qt界面
  • Dockerfile与Docker网络
  • 人造草坪市场分析:预计2029年将达到328亿元
  • 基于Java SSM框架实现弹幕视频网站系统项目【项目源码+论文说明】计算机毕业设计
  • 关于一些整理图像及视频数据的代码块
  • 从钓鱼邮件溯源到反制上线
  • 【深度学习】Adversarial Diffusion Distillation,SDXL-Turbo 一步出图
  • 股市复苏中的明懿金汇:抓住新机遇
  • 【C#】使用CancellationToken终止一个正在运行的Task