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

ch05 pointer

指针

指针声明 [1]

指针/指针变量 (pointer) 是用于存储地址的变量

使用 & 运算符 来访问变量的地址。例如

#include <stdio.h>

void main()
{
   int a = 100;
   printf("%x", &a);
}

输出结果为 16进制的内存地址

61fe1c

使用地址运算符 * 可以从变量地址中获取变量的值,这个行为被称为间接引用/解引用(indirection/dereferencing)。例如:

#include <stdio.h>

void main()
{
   int a = 100;
   printf("%d", *(&a)); 
   // 也可以写为,因为*与&优先级相同,从右到左的顺序,所以有没有()意思是相同的
   printf("%d", *&a);
}

输出结果为 100

指针变量

指针变量是指存储一个变量的地址的变量,可以使用符号 * 来修饰变量,定义语法为:

dataType *pointerVariableName = &variableName;

例如,下面的两个输出结果是相同的地址

#include <stdio.h>

void main()
{
   int a = 100;
   int *pointer;
   pointer = &a;
   printf("address of a is: %x\n",&a);
   printf("address of pointer is: %x\n",pointer);
}

address of a is: 61fe14
address of pointer is: 61fe14

修饰符说明

修饰符说明
*两个用途:
指针变量的声明
返回被引用变量的值
&返回变量地址

使用const修饰指针 [3]

使用关键字 const 修饰的指针变量是不能改变指针变量所指向的地址的变量,通俗来讲即不能被改变值的指针变量

声明语句

<type of pointer> *const <name of pointer>;  
int *const ptr;  

const位置:const修饰的部分(const所在位置)不可改变

  • 例如 const int *p;int const *p; 这里修饰的都是 *p

    • *p (变量地址)不能被改变

    • p (变量值)可以被改变

    •   #include<stdio.h>
        #include<stdlib.h>
        void main()
        {
          int a = 10;
          int b = 20;
          int const *p;
          *p = &b; // result assignment of read-only location '*p'
          p = 30; // result 30
        
          printf("%d",p);
        }
      
  • int * const p; const在*后p前,这里修饰的都是 p

    • p不可以被修改
    • *p 可以被修改
  • const int *const p; 这里 const 修饰 * 和 p,所以两个都不可修改

Notes:通常情况下常用只有第一种情况

使用场景:最为参数形参修饰该参数为只读参数,例如 printf

int printf (const char *__format, ...)

指针的类型 [2]

C语言中包含多种指针类型:

  • 空指针(Null Pointer)
  • 野指针(Wild Pointer)
  • 悬空指针(Dangling pointer)
  • 泛型指针(void Pointer)
  • 一些早期Dos中的概念
    • 近指针(Near):不能存储大小大于 16 位的地址
    • 远指针(Far):32 位大小的指针
    • 大指针(Huge):类似于远指针。

空指针

在声明期间将 NULL 分配给指针的指针称为 Null 指针,例如

#include <stdio.h>

void main()
{
   int *var = NULL;
   printf("address of var is: %p\n",var);
}

泛型指针

使用 void 关键字声明指针变量,可以接受任意一种类型的变量地址,如果需要使用泛型指针,需要强转为对应类型才可以使用。如下示例:

#include <stdio.h>

void main()
{
   int a = 6666;
   void *p = &a;
   printf("address of p is: %p\n", (int*) p);
   printf("value of p is: %d\n", *(int*) p);
}

address of p is: 000000000061FE14
value of p is: 6666

野指针

野指针是指,没有有效地址的空间的指针,例如声明了指针变量没有对其赋值,这种情况下会出现 Segmentation fault 异常。

#include <stdio.h>

void main()
{
   int *p;
   printf("address of p is: %p\n", *p); 
}

再例如一个无效的地址空间也会发生 Segmentation fault 异常;例如下列赋值中,指针p赋值被视为一个内存地址,而不是变量的值,这个地址无效。

#include <stdio.h>

void main()
{
   int *p;
   *p = 10000;
   printf("address of p is: %p\n", *p);
}

悬空指针

悬空指针是指”已经被释放的内存“的指针变量,此时这个地址空间是无效的。如下所示

#include<stdio.h>
#include<stdlib.h>
void main()
{

  int *P=(int *)malloc(sizeof(int));
  int a=5;
  P=&a;
  free(P);
  printf("After deallocating its memory *p=%d", *P);
}

指针数组

在C语言中 数组是由两部分组成,数组名与数组本身。

#include<stdio.h>
void main()
{
	int a[10] = {0};
	a[1] = 20;
	for (int i=0; i< sizeof(a) / sizeof(a[0]); i++){
		printf("%d\n",a[i]);
	}
	printf("arr %p = &arr %p", a, &a[0]);
   
}

上面例子中,变量a是一个数组,而变量a代表的是一个指向该数组第一个元素的地址,a=&a[0] ,这是一个const修饰的指针是不可以被改变的。

可以看到变量a和 &a[0] 值是相同的,而一个const修饰的指针变量是不可改变的,故a不能被赋值,下列代码是不合法的。总结为:不允许将任何地址分配给数组变量

#include<stdio.h>
void main()
{
	int a[10] = {0};
	int b[1] = {0};
	int c = 10;
	a = &b;
	a = &c;
	for (int i=0; i< sizeof(a) / sizeof(a[0]); i++){
		printf("%d\n",a[i]);
	}
	printf("arr %p = &arr %p", a, &a[0]);
   
}

使用指针访问数组

上面知道了,数组变量指向的是数组的起始元素(第一个元素)的地址指针,那么通过指针可以对数组进行访问。

因为 a = &a[0] 那么 a[0] = *a,由此可推导出下列公式:

  • &a[1] = a+1 (取数组元素的地址) 那么 a[1] = *(a+1) (取数组元素的值)
  • &a[2] = a+2 那么 a[2] = *(a+2)

这种情况下数组的访问就有四种方法

Array VS Pointer [4]

  • 数组名是常量,指针是变量
  • sizeof(array) 得到的是数组实际占用内存空间的字节数,sizeof(pointer) 是4/8 取决于操作系统

指针运算

左值和右值 [5]

了解对于指针运算前,需要对左值(lvalue)和右值(rvalue)进行了解

  • 左值:通常来说是在占有内存地址(即具有地址)的对象
    • 具有存储数据的内力,例如变量
    • 不能是函数,表达式,或常量
    • 综合来说,左值可以是以下几种:
      • 任何类型的变量:int, float, pointer, struct等
      • 数组的下标表达式,如a[1]
      • 括号内的表达式(指针)
      • 指针的间接引用
      • 常量(不可改变的左值)
      • 通过指针访问对象属性或成员 (-> or .)
  • 右值:在内存中没有占有内存地址的对象
    • 返回不可改变的表达式或值,例如a+b是一个常量,函数运行结果是一个右值

左值的示例

#include<stdio.h>
void main()
{	
	// 声明变量a为int类型
	int a;
	// a是一个左值,引用对象为int
	a = 1;
	// 左值a出现在右边的场景
	int b = a; 
	
	// 非法,a是左值
	9 = a;

	// 左值,*p是指针,p就是值,*p+4是后面一个int类型的地址,是左值
	int *p;
	int a = 10;
	p = &a;
	// 这里实际上是指针运算,p+0为自己,p存储指针 *p为值,那么a=10000,p=10000
	*(p+0) = 10000;   
	printf("%p\n",a);
	printf("%p\n",*p);
	printf("%d\n",a);
	printf("%d\n",*p);
}

右值的示例

// 声明变量a b
int a = 1, b;

// 非法,a+1为常量,不是左值
a + 1 = b; 
 
// 声明指针变量 p q
int *p, *q; // *p, *q 为左值
 
*p = 1; // 合法,左值可以赋值
 
// 非法 - "p + 2" 是右值
p + 2 = 18;
 
q = p + 5; // "p + 5" 是右值,合法
 
// 解引用表达式是左值
*(p + 2) = 18;
 
p = &b;
 
int arr[20]; // 数组元素访问arr[12] = *(arr+12) 所以是左值有效
 
struct S { int m; };
 
struct S obj; // obj and obj.m are lvalues
 
// ptr-> 等于  (*ptr).m 是左值有效

一元表达式需要有左值,当a是左值&a才生效,12本身是一个右值,不能&12

int a, *p; // a 和 *p都是左值
p = &a; // 合法,&a是常量为右值,p是左值
&a = p; // 缺少左值,非法

三元表达式是一个右值(C++是左值)

(  x < y ? y : x) = 10; // c无效,c++有效

左值 VS 右值

  • 左值为内存中可识别的对象,右值为一个常量(广义上,不是const)
  • 左值可以在左边和右边,右值必须在右边
  • 右值必须有左值才生效
  • 指针运算可左可右,变量运算是右值

arithmetic [6]

C语言中,指针支持四种算术运算符,吧地址当作数值进行算数运算

运算符说明
=可以将值赋值给指针
+从指针加整数值以指向不同的内存位置。
-从指针中减去整数值以指向不同的内存位置
比较运算(==, !=, <, >, <= , >=)仅比较两个指针地址,例如
pointer == NULL
++指针使用递增运算符将向前位移一位
指针使用递减运算符将向后位移一位
  • 当对指针变量进行递增和递减操作时,会改变指针变量本身所在地址空间
  • 当对指针变量进行±运算时,不会改变指针变量本身

指针数组

指针数组是指数组存储的内容是指针,即数组内所有的元素都是指针

#include<stdio.h>
void main()
{	
	int a = 10;
	int b = 20;
	int c = 30;
	int *arr[] = {&a, &b, &c};
}

指针数组本质也是一个多级指针,例如一个2D数组每行(rows) 存储的值是一列(colums)的地址

#include<stdio.h>
void main()
{	
	int a[] = { 10 };
	int b[] = { 20 };
	int c[] = { 30 };
	int *arr[] = {a, b, c};
}

Pointer VS Array

  • sizeof
    • sizeof(array) 返回数组中所有元素占用内存的大小
    • sizeof(pointer) 只返回指针变量本身用内存的大小
  • &运算符
    • 数组名是 &array[0] 的别名,返回数组中第一个元素的地址
    • &pointer 返回指针的地址
  • 指针变量可以赋值,而数组变量不可以
  • 数组是收集了相同类型元素的集合,而指针是一个存储地址的变量

Reference

[1] c pointer

[2] pointer type

[3] const pointer

[4] pointer VS array

[5] lvalue VS rvlaue

[6] pointer arithmetic

[7] [pointer arithmetic](

相关文章:

  • Java:Kubernetes原生Java与Quarkus
  • 15天深度复习JavaWeb的详细笔记(十二)——综合案例
  • AD生成Gerber及CAM350检查
  • Python数据分析:折线图和散点图的绘制
  • 【Vue2基础】Vue项目搭建及组件使用
  • 艾美捷衣霉素Tunicamycin 化学性质及引用文献
  • 【小样本分割】Self-Support Few-Shot Semantic Segmentation
  • ch01变量和数据结构
  • 五分钟学会一门编程语言?
  • 【Python数据分析 - 11】:DataFrame索引操作(pandas篇)
  • [Vue]数据代理
  • VSCODE 系列(二)常用插件
  • 【zabbix】解决zabbix在web页面显示中文乱码问题
  • ESP32/ESP8266自动下载电路波形,ESP32/ESP8266不能UART流控自动下载的解决方法
  • c语言分层理解(枚举和联合体)
  • [译]如何构建服务器端web组件,为何要构建?
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 78. Subsets
  • in typeof instanceof ===这些运算符有什么作用
  • Javascript编码规范
  • js 实现textarea输入字数提示
  • js对象的深浅拷贝
  • Node项目之评分系统(二)- 数据库设计
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 分布式熔断降级平台aegis
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 手写双向链表LinkedList的几个常用功能
  • 微信小程序开发问题汇总
  • Prometheus VS InfluxDB
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (1)bark-ml
  • (4)STL算法之比较
  • (LeetCode 49)Anagrams
  • (python)数据结构---字典
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (二)PySpark3:SparkSQL编程
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (四)图像的%2线性拉伸
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • (转)项目管理杂谈-我所期望的新人
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .Net IOC框架入门之一 Unity
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .NET企业级应用架构设计系列之结尾篇
  • .NET项目中存在多个web.config文件时的加载顺序
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • @autowired注解作用_Spring Boot进阶教程——注解大全(建议收藏!)
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [AUTOSAR][诊断管理][ECU][$37] 请求退出传输。终止数据传输的(上传/下载)
  • [AutoSar]BSW_Memory_Stack_003 NVM与APP的显式和隐式同步