首页 > 编程语言 >C++ 指针学习笔记

C++ 指针学习笔记

时间:2023-11-17 19:33:17浏览次数:27  
标签:指向 int 笔记 C++ 数组 array ptr 指针

C++ 指针学习笔记

引入

指针是什么

指针是一个变量,其值为另一个变量的地址。

指针声明的一般形式为:

type *ptr_name;

type 是指针的基类型,ptr_name 是指针的名称,* 用来指定一个变量是指针

对于一个指针,需要明确四个方面的内容:指针的类型指针所指向的类型指针的值指针所指向的内存区)、指针本身所占据的内存区

指针的类型

从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型:

int *ptr; // int*
char *ptr; // char*
int **ptr; // int**
int (*ptr)[3]; // int(*)[3]
int *(*ptr)[4]; // int*(*)[4]

指向类型

  • 所指向对象的类型

指针所指向对象的类型一般被称为 指针的类型

从语法上看,只须把指针声明语句中的指针名字和名字左边的指针声明符 * 去掉,剩下的就是指针所指向的类型:

int *ptr; // int
char *ptr; // char
int **ptr; // int*
int (*ptr)[3]; //int ()[3]
int *(*ptr)[4]; //int *()[4]
  • 间接访问

在用指针间接访问时,指针所指向的类型决定了编译器如何看待那片内存区中的内容:

char *ptr = &a;
// 假设 p 指向的地址为 4000,那么编译器默认 ptr 指向的是 4000 这一个字节的内容
int *ptr = &a;
// 编译器会认为 ptr 所指向的对象是由 4000, 4001, 4002, 4003 四个字节共同组成
double *ptr = &a;
// 编译器会认为 ptr 所指向的对象是由 4000 ~ 4007 八个字节共同组成
  • 指针的算术运算

指针的类型影响指针的算术运算,如对于 p + 1 ,系统会将指针 p 的值加上 sizeof(type)

指针的值(指针所指向的内存区或地址)

指针的值是指针本身存储的数值,这个值被编译器视作一个地址。

指针所指向的内存区以指针的值为起始地址,长度为 sizeof(type) 的一片内存区。

指针本身所占据的内存区

字节长度为 sizeof(p)

指针的定义

初始化和赋值

指针的初始化和赋值只能使用一下四种值:

  • 0NULL
  • 相同指向类型的对象的地址

NULL 指针

NULL 指针是一个定义在标准库中的值为 0 的常量。

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为 空指针

int *ptr = NULL;
// or
int *ptr = 0;

指针的 const 限定

指向只读型对象的指针必须有 const 限定,称为 指向 const 对象的指针,定义形式为:

const type *ptr_name;
  1. 把只读型对象的地址赋值给 非指向 const 对象的指针 是错误的;
  2. 可以把非只读型对象的地址赋值给 指向 const 对象的指针,但不能通过 指向 const 对象的指针 间接修改指向对象;
  3. 实际编程过程中,指向 const 的指针 常用作函数的形参,以此确保传递给函数的参数对象在函数中不能被修改。

const 指针

只读型指针(或 常量指针)的定义为:

type* const ptr_name;

需要注意的是 const 放在变量名前,与指向 const 对象的指针定义形式不同。

不能改变只读型指针的值,但是可以通过只读型指针间接修改指向对象

指针的运算

指针的算术运算

指针的四种算术运算:+, -, ++, --

指针算术运算会根据指针所指向的类型和大小来决定移动的距离,即

执行 ptr++ptr-- 后,指针 ptr 的值会增加 sizeof(type),指向下一个该类型元素的地址。

指针的比较

指针可以用关系运算符 ==, <, > 进行比较。

两个相比较的指针所指向的类型应当相同,即指向相关变量。

指针与指针之间的减法运算

可以计算两个指针之间的距离,指针间减法运算的值即为指针地址的差值除以 sizeof(type)。可以计算两指针之间的距离。

指针之间没有加法运算。

指针与数组

数组与指针

C++ 规定数组名即代表 数组本身,又代表 整个数组的地址,还是 数组首元素的地址。即,声明一个数组

type array[N];

那么数组名 array 就有了两重含义:

  1. array 代表整个数组,类型为 type[N]

  2. array 是一个 常量指针,类型为 type*,指向内存区为数组首元素。

    注意该指针占有单独的内存区,与数组首元素占据的内存区不同。

不同表达式中数组名有不同含义:

sizeof(array); // 代表数组本身,sizeof(array) 为数组定义的字节 (bytes) 数,即 N * sizeof(type)
*arrray; // 代表指针,值为 array[0] 即首元素的值
sizeof(*array); // 值为数组单元的大小,即 sizeof(type)
array + k; // 代表指针,类型为 type*,指向的类型为 type 即数组的第 n 个元素
sizeof(array + k); // 值为指针类型的大小,即 sizeof(type*)

指针与一维数组

定义(指向一维数组的指针)

定义指向一维数组的指针变量时,指向类型应与数组元素类型一致:

int array[N];
int *ptr = &array;
int *ptr = array;
int *ptr = *array[0];
// 根据数组名的性质,以上三种定义防止等价,指针 ptr 的值都为数组首元素的地址
int *ptr = *array[k];
// 也可以将数组某一元素的地址赋值给指针

一维数组的访问方式

由于数组的元素地址是规律性增加的(连续的),根据指针算术运算规律,可以利用指针及其算术运算来访问数组元素。

参照 定义 中的声明,以下访问 array[i] 的方式等价:

  1. 数组下标法:array[i]
  2. 指针下标法:ptr[i] (指针 ptr 的值为数组首元素的地址,此时 ptr 和数组名等价)
  3. 地址引用法:*(array + i)
  4. 指针引用法:*(ptr + i)

定义 int array[N], *ptr = array;,需注意 ptr 和数组名 array 并不完全等价。

比如由于 array 是一个常量指针,所以不允许修改,如 array ++ 是不正确的语法。

遍历一维数组

首先定义 int array[N];

以下为遍历一维数组的几种方式:

  1. 下标法:

    for (int i = 0; i < N; ++ i)
        array[i];
    
  2. 通过地址间接访问:

    for (int i = 0; i < N; ++ i)
        *(array + i)
    

    以上两种方法似乎等价(?),笔者并未严格考究。

  3. 指针访问法:

    for (int *p = array; p < a + N; ++ p)
        *p;
    

    用指针作为循环变量(利用了指针的比较),优点是指针直接指向元素,无须重新计算地址,能提高运行效率。

    以下访问方式与前两种等价

    int *p = array;
    for (int i = 0; i < N; ++ i)
        *(p + i);
    

指针与二维数组

数组的数组

定义二维数组

int array[N][M];

那么,数组名 array 代表二维数组首元素的起始地址,注意 首元素 并非整型,而是由 N 个整型元素构成的一维数组。即一下表达等价:

array;
array[0];
array + 0;
*array;

此时 a[k] 即是一维数组的数组名。

定义(指向二维数组的指针)

二维数组定义如上。

int (*ptr)[M] = array;

注意此时 ptr 指向的是二维数组中的首元素(一维数组 array[0]),ptr ++ 则是将 ptr 指向 array[1](而不是 array[0][1])。

二维数组的访问方式

二维数及指针定义如上,以下访问方式等价:

a[i][j];
*(a[i] + j);
*(*(a + i) + j);
*(*(ptr + i) + j);

指针数组

声明方式为:

type *ptr[N];
// 在 C++ 中,[] 的优先级高于 *,所以 ptr 会先与 [] 结合;
// 此处 ptr 为一个数组,类型为 type **,数组的元素为类型为 type* 的指针
type *(ptr[N]);
// 两种写法等价

指向指针的指针(多级间接寻址)

声明方式为:

type **ptr_name;

定义以下二级指针:

int var;
int *ptr = &var;
int **pptr = &ptr;

此时 pptr 的值为 ptr 的地址,如需通过 pptr 间接访问 var,则需要两个星号,即 **pptr

指针与结构类型

可以声明一个指向结构类型对象的指针:

struct Struct_name {
    member_type1 member_name1;
    member_type2 member_name2;
    member_type3 member_name3;
} object_name;
struct Struct_name *ptr = &object_name;

访问成员变量的方式如下:

  1. ptr -> member_name1;
    
  2. (*ptr).member_name1;
    
  3. *ptr;
    *(ptr + k);
    

    注意第三种访问方式是不正规的,这是因为某些情况下,结构对象的相邻成员变量间可能会有若干“填充字符”(如字对齐、双字对齐等,即成员变量的内存地址不连续)。

指针与函数

传递指针给函数

C++ 允许传递指针给函数,只需要声明函数参数为指针。

传递指针给函数,大致有如下用法:

  1. 函数内通过指针间接访问或修改指针所指向的对象;

  2. 通过 const 限定的指针防止函数内修改指针所指向的对象;

  3. 传递数组给函数,如

    int myFunction(int *arr) {
        for (int i = 1; i < N; ++ i)
            arr[i];
       	// 此时 arr 就是指向一维数组的指针
    }
    int main() {
        int arr[N];
        myFuntion(arr);
        return 0;
    }
    

从函数返回指针

C++ 允许从函数返回指针,需要声明函数的返回值为指针类型:

int * myFuntion() {
	//   
}

注意,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。

以下程序生成 N 个随机数,并返回表示指针的数组名(数组首元素的地址):

#include <iostream>
#include <ctime>
#include <cstdlib>

using namespace std;

#define N 10

int * getRandom() {
    static int r[N];
    // 注意不能在函数外返回局部变量的地址,所以需要定义 r 为 static 变量
    srand((unsigned)time(NULL));
    for (int i = 0; i < N; ++ i)
        r[i] = rand();
   	return r;
}

int main() {
    int *ptr = getRandom();
    return 0;
}

指向函数的指针

函数指针的声明如下:

int myFuntion(char *, int) {}

int (*ptr)(char *, int);
int var = *ptr("Hello", k);
// 通过函数指针调用函数

指针与字符串

通过字符型指针来处理字符串,其过程与通过指针访问数组元素相同。

定义方式如下:

char *ptr = "Hello World!";
// 初始化定义指向字符常量回导致编译器警告
char str = "Hello World!";
char *ptr = str;

也可以定义一个指针数组存储多个字符串:

char *str[4] = { "Hello", "World", "!" };

用指针遍历字符串:

char str[] = "Hello World!", *ptr = str;
while (*p) cout << *p++;
// 字符串结束表示符 '\0' ASCALL 码为 0,或用以下方式判断结束
while (*p != '\0') cout << *p++;

标签:指向,int,笔记,C++,数组,array,ptr,指针
From: https://www.cnblogs.com/buzzhou/p/17839530.html

相关文章

  • MSYS2学习笔记:基础使用
    本随笔用于记录随笔作者在使用MSYS2工具过程中需要掌握到的基础知识,例如如何查找想要的工具与其下载、如何环境配置等基础操作。本文内容摘录自MSYS2官方文档该随笔会不定时更新。随笔作者还正处于学习阶段,难免出现技术上和书写上的问题,如果发现类似问题,欢迎在评论区或私信与我......
  • C++从零复习
    0.前言子曰:“温故而知新,可以为师矣。”学习了知识,不复习怎么行。这篇随笔是为C++小白写的复习资料,根据目录使用效果更佳。悄悄告诉你:听课的时候走神了也可以查缺补漏哦1.顺序结构(1)框架头文件#include<clude_name>//将名为“clude_name”的头文件导入//常用头文件实例#i......
  • C++ STL String用法
    string在C语言中,提供了字符串的操作,但只能通过字符数组的方式来实现字符串。而string则是一个简单的类,使用简单,在OI竞赛中被广泛使用。相较于其他STL容器,string的常数可以算是非常优秀的,基本与字符数组不相上下。string常用操作输出strings="123";printf("%s......
  • 《A Survey on Deep Learning-based Fine-grained Object Classification and Semanti
    论文标题《ASurveyonDeepLearning-basedFine-grainedObjectClassificationandSemanticSegmentation》基于深度学习的细粒度对象分类和语义分割的综述为什么是“Object”而不是“image”作者西南交通大学和新加坡国立大学2016年7月1日received;2016年9月30日......
  • 【C++中cin在Qt输出终端无法手动输入问题解决办法(详细)】
    现象:在Qt中使用cin进行对一个变量z进行输入,然后在用cout对z进行输出,结果没有进行手动输入,程序自动凭空出现类似512,32759等一些数值输出。 解决办法:第一步:在Qt左侧项目栏,在.pro文件中添加一行代码CONFIG+=console 第二步:在项目--运行--勾选在终端中运行(Runinterminal) 配置......
  • 硬件开发笔记(十二):RK3568底板电路电源模块和RTC模块原理图分析
    前言  做硬件做系统做驱动,很难从核心板做起,所以我们先依赖核心板,分析底板周围的电路,然后使用AD绘制原理图和设计PCB,打样我司测试底板,完成硬件测试,再继续系统适配,驱动移植,从而一步一步完善成为一个功能完善的底板,且搭载了我们跳完的系统和驱动。  本篇文章,先从底板的电源电......
  • 学习笔记10
    第十二章学习笔记块设备I/O缓冲区I/O缓冲的基本原理非常简单。文件系统使用一系列I/O缓冲区作为块设备的缓存内存。当进程试图读取(dev,blk)标识的磁盘块时,它首先在缓冲区缓存中搜索分配给磁盘块的缓冲区。如果该缓冲区存在并且包含有效数据,那么它只需从缓冲区中读取数据,......
  • 个人笔记
    一、leaderwork对上1、明确上面的工作目标,对自己的工作规划和期许2、做的不对的地方,要记下来,主动找领导过解决的方案3、脑图产出你的工作整体计划,解决top3的问题4、工作中,标明经过自己的努力,漏测率降低了,什么什么提效了,结果数据化表格对下1、有现有的流程就按照现有的流程,例如......
  • HW学习笔记
    栈库分离方法注意事项:  所有用户输入数据需要进行分离过滤,不能遗漏、选择安全的过滤函数如mysql_real_escape_string(),避免过滤不严格导致注入  SQL查询模板需要设计安全,米面语义可以被注入数据改变,函数Mysql_query()等执行SQL预警也需要参数化,避免拼接导致注入  ......
  • 点分治学习笔记(未完成)
    前言点分治不应该算数据结构,它的本质是分治的思想。问题引入对于一个序列\(a\),求是否存在\((l,r)\)使得\(\sum\limits_{i=l}^{r}a_i=k\)。\(n\le10^6,|a_i|\le10^9\)。本题显然是有其它的做法的,由于学的是点分治,所以考虑分治做法。首先对\(a\)求前缀和,记这个数组为......