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

C指针(二)

原文链接:http://www.orlion.ga/924/

一、指针与const限定符

    const限定符与指针结合起来常见的情况有一下几种:

const int *a;
int const *a;

    这两种写法是一样的,a是一个指向const int型的指针,a所指向的内存单元不可改写,所以(*a)++是不允许的,但a可以改写,所以a++可以。

int * const a;

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

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 char *型。printf函数原型的第一个参数是const char *型,可以把char * 或const char *指针传给它。

 

二、指针与结构体

 

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

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 = π

    这样定义后,表达式*ppi取pi的值,表达式**ppi取i的值。

    数组中的每个元素可以是基本类型,也可以是复合类型,因此也可以是指针类型。如定义一个数组a由十个元素组成,每个元素都是int *指针:

int *a[10];

    这称为指针数组

    

    main函数的原型是int main(int argc, char *argv[]);。argc是命令行参数的个数。而argv是一个指向指针的指针而不是指针数组。函数原型中的[]表示指针而不表示数组,等价于char **argv。为什么不写成char **argv而写char * argv[]呢?主要是为了给读代码的人提供有用的信息,argv不是指向单个指针,而是指向指针数组的首元素。数组中每个元素都是char *指针,指向一个命令行参数字符串。

 

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

 

    以下定义一个指向数组的指针,数组有10个元素:

int (*a)[10];

    我们可以认为[]比*有更高的优先级,如果a先和*结合则表示a是一个指针,如果a先和[]结合则表示a是一个数组。现在看指向数组的指针怎么用:

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

    a是一个数组,在&a这个表达式中,数组名做左值,取整个数组的首地址赋给指针pa。注意&a[0]表示数组a的首元素的首地址,而&a表示数组a的首地址,显然这两个地址相同,但这两个表达式的类型是两种不同的指针类型,前者是int *,而后者类型是int (*)[10]。*pa就表示pa所指向的数组a,所以取数组的a[0]元素可以用表达式(*pa)[0]。注意到*pa可以写成pa[0],所以(*pa)[0]这个表达式也可以改写成pa[0][0],pa就像一个二维数组的名字,它表示什么含义呢?下面把pa和二维数组放在一起分析。

    int a[5][10];和int (*pa)[10];之间的关系类似int a[10];和int *pa之间的关系:a是由一种元素组成的数组,pa则是指向这种元素的指针。所以如果pa指向a的首元素:

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

    则pa[0]和a[0]取得是同一个元素,唯一比原来复杂的地方在于这个元素是由10个int组成的数组,而不是基本类型。这样,我们可以把pa当成二维数组名来用,pa[1][2]和a[1][2]取的也是同一个元素,而且pa比a用起来更灵活,数组名不支持赋值、自增等运算,而指针可以支持,pa++使pa跳过二维数组的一行(40个字节),指向a[1]的首地址。

 

五、函数类型和函数指针类型

    

    在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是一种函数类型类似于数组类型,做右值使用时自动转换成函数指针类型。当然也可以写成void (*f)(const char *) = &say_hello;把函数ay_hello先取地址再赋给f,不需要自动类型转换。

    可以通过函数指针来调用函数:f("Guys"),也可以先用*f取出它所指的函数类型再调用函数:(*f)("Guys")。

    

    可以先定义函数类型F:

typedef int F(void);

    这种类型的函数不带参数,返回值是int,可以这样声明f和g:

F f, g;

    相当于声明:

int f(void);
int g(void);

    下面这个函数声明是错误的:

F h(void);

    因为函数可以返回void类型、标量类型、结构体、联合体,但不能返回函数类型,也不能返回数组类型。而下面这个函数声明时正确的:

F *e(void);

    函数e返回一个F *类型的函数指针,但是这样:

int (*fp)(void);

    是声明了一个函数指针,而不是声明一个函数。e也可以这样声明:

F *fp;

转载于:https://www.cnblogs.com/orlion/p/5350782.html

相关文章:

  • 用Python开发自动化测试脚本
  • git简单命令
  • 程序员健康Tips
  • 如何从mysql备份中提取单张表数据
  • AVAudioSessionCategory说明
  • 一个Android项目多线程下载模块开源库:AndroidFileDownloader
  • Azkaban的Web Server源码探究系列12: 首页之前的跳转
  • 学习之路-现代密码学基础-001
  • 使用hadoop实现关联商品统计
  • 链表的逆置
  • 帧间提取水印
  • 面试遇到的一些题
  • Charles中如何对https抓包
  • 大家一起和snailren学java-(13)字符串
  • 九、搭建织梦cms网站
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • 【node学习】协程
  • Brief introduction of how to 'Call, Apply and Bind'
  • ES6 学习笔记(一)let,const和解构赋值
  • Javascript 原型链
  • JS笔记四:作用域、变量(函数)提升
  • Redis的resp协议
  • vue-router 实现分析
  • 基于web的全景—— Pannellum小试
  • 简单基于spring的redis配置(单机和集群模式)
  • 看域名解析域名安全对SEO的影响
  • 聊一聊前端的监控
  • 容器服务kubernetes弹性伸缩高级用法
  • 十年未变!安全,谁之责?(下)
  • 使用parted解决大于2T的磁盘分区
  • 世界上最简单的无等待算法(getAndIncrement)
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 优秀架构师必须掌握的架构思维
  • 06-01 点餐小程序前台界面搭建
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #LLM入门|Prompt#3.3_存储_Memory
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (Python第六天)文件处理
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (论文阅读11/100)Fast R-CNN
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (译)2019年前端性能优化清单 — 下篇
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)Sql Server 保留几位小数的两种做法
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Mobi域名介绍
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)