C++ 指针学习笔记

这篇具有很好参考价值的文章主要介绍了C++ 指针学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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)。32 位机器下指针类型的大小为 4 字节,64位机器下指针类型的大小为 8 字节。

指针的定义

初始化和赋值

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

  • 0NULL
  • 相同指向类型的对象的地址
  • 对象存储空间后面下一个有效地址,如数组下一个元素的地址

空指针:NULL 和 nullptr

NULL 指针是一个定义在标准库中的值为 0 的常量(C++11前)。

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

int *ptr = NULL;
// or C++11 前
int *ptr = 0;

需要注意的是,在 C++11 之前,C++ 和 C 一样使用 NULL 宏表示空指针常量,C++ 中 NULL 的实现一般如下:

// C++11 前
#define NULL 0

但是空指针和整数 0 的混用会导致很多问题,比如:

int myFunction(int var);
int myFunction(int *ptr);

在调用 myfunction(NULL) 时,实际调用的函数类型是 int(int) 而不是 int(int *)

C++11 中引入 nullptr 关键字作为空指针常量,且规定 nullptr 可以隐式转换为任何指针类型,转换结果为该结果的空指针值。

nullptr 的类型为 std::nullptr_t,称为空指针类型,可能实现如下:

namespace std {
typedef decltype(nullptr) nullptr_t;
}

另外,C++11 其 NULL 的宏定义也修改为:

// C++11 起
#define NULL nullptr

C 语言中,C23 也引入了 nullptr 作为空指针常量,同时引入 nullptr_t 作为其类型。

野指针及悬挂指针

野指针 指的是指向已经释放的内存区域或无效内存地址的指针,有三种情况:

  1. 未初始化;
  2. 指针失效:
    • 使用 malloc 分配的内存被 free 释放;
    • 局部变量在离开局部命名域后被自动销毁,如函数或循环内部临时声明的变量;
  3. 越界访问数组;

在使用野指针时,程序可能会访问无效的内存区域,导致程序崩溃或产生不可预测的结果。

悬挂指针是指指向已被释放或不再有效的对象的引用。在某些情况下,如果指向对象的引用在对象被释放或销毁后仍然存在,那么这个引用就成为悬挂引用。这可能发生在指针或引用指向的对象在其生命周期内被销毁或释放后,仍然尝试访问该对象。——ChatGPT

声明指针变量时正确地赋初值以避免野指针问题。

指针的 const 限定

指向只读型对象的指针

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

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

const 指针

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

type* const ptr_name;

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

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

多级指针的 const 限定

const 限定大概可以分为两类:

  1. 限定最终指向的对象或数组元素,此时 const 在类型前。(类同 指向只读型对象的指针
  2. 限定指针变量,此时 const 在指针变量或引用的指针前面。
const int * const * const pptr;
// **pptr 指向常整型
// * ptr 指向常指针
// ptr 本身是常指针

const 不宜出现在类型和 * 之间,即一般不写成 type const * ptr 的形式。

指针的运算

指针的算术运算

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

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

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

指针的比较

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

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

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

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

int arr[] = {0, 1, 2, 3, 4, 5};
int *p = arr;
int *q = &arr[sizeof(arr) / sizeof(int)];
printf("sizeof(arr) = %d", q - p);
// 输出为 6

指针之间没有加法运算。

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

声明方式为:

type **ptr_name;

定义以下二级指针:

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

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

指针与数组

数组与指针

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;

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

通过指针间接访问二维数组的方式有多种,以下简要讲述几种。

数组指针访问

利用二维数组中 array[k] 即一维数组的数组名的特性,定义指向 一维数组的指针

int (*ptr)[M] = array;

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

访问方式与直接访问基本一致:

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

或者也可以直接使用 指向二维数组的指针

int (*ptr)[M][N] = (int(*)[M][N]) array;

由于编译器会认为 array 的类型为 int (*)[3],所以直接赋值会导致编译错误 (C++) 或 警告 (C);因此初始化时需要 显式类型转换

访问方式与上一种有所区别:

(*ptr)[i][j];

二级指针访问

也可以使用指针的指针即二级指针,但是在 C++ 中直接赋值会导致错误:

int **ptr = array;
// C++ 下编译器报错 "cannot convert 'int(*)[N]' to 'int **'",但是 C 标准下只会给出警告
// 但这样子会丢失数组的结构信息,导致无法使用诸如 ptr[i][j] 的方法访问元素
// 即编译器会认为 *ptr 指向的类型为 int* 而并非 int [M],此时 ptr + 1 的地址偏移值为 sizeof(int*) 而非 sizeof(int [M])

int *mp[N];
for (int i = 0; i < N; ++ i)
    mp[i] = array[i];
int **pptr = mp;
pptr[i][j] /* ... */;
// 使用这种“指针数组转译”的方式便可以保证 pptr[i][j] 正确的访问元素 (i, j)
// 

// 动态创建二维数组时,也会使用到二级指针
int **arr = (int**)malloc(M * sizeof(int*));
for (int i = 0; i < M; ++ i)
    arr[i] = (int*)malloc(N * sizeof(int));
arr[i][j] /* ... */;
// 可以使用 arr[i][j] 访问元素 (i, j)
// 其实这两种方式是类似的,都含有“指针数组”的思想。

指针与结构类型

指向结构类型对象的指针

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

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++ 中声明时可以直接使用 Struct_name 作为结构类型,而不需要 struct Struct_name

结构类型的嵌套

可以通过指针实现结构类型的嵌套,比如实现一个列表:

typedef ListNode *Node;
struct ListNode {
    int val;
    Node next;
};

// 遍历列表
for (Node cur = head; cur; cur = cur->next)
    /* ... */
// 设 head 为指向列表头指针,列表末尾的 next 指针指向 NULL

指针与函数

传递指针给函数

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

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

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

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

  3. 传递数组给函数,如

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

    传递多维数组给函数时,需要指明除第一维之外的长度。即多维数组形参除第一维外,长度必须是常量。

    编译器自动忽略第一维长度,如形参 int arr[10][3] 编译解释 int arr[][3]

    或者可以通过一下方式传递二维数组给函数

    int myFunction1(int *p) {
        int (*arr)[N] = (int (*)[N]) p;
        int (*ptr)[M][N] = (int (*)[M][N]) p;
        /* ... */
    }
    int myFunction2(int **arr) {
        /* ... */
    }
    int main() {
        int array[M][N];
        myFunction1(&array[0][0]);
    
        int *mp[N];
        for (int i = 0; i < N; ++ i)
            mp[i] = array[i];
        myFunction2(mp);
        // “指针数组转译”
    
        int **arr = (int**)malloc(M * sizeof(int*));
        for (int i = 0; i < M; ++ i)
            arr[i] = (int*)malloc(N * sizeof(int));
        myFunction2(arr);
    }
    

从函数返回指针

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

int * myFuntion() {
    static int *p;
    /* ... */
    return p;
}

注意,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();
    for (int i = 0; i < N; ++ i)
        cout << *(ptr + i);
    return 0;
}

指向函数的指针

函数指针 的声明如下:

int myFuntion(char *, int) {}

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

可以通过函数指针实现 动态 调用函数,同时函数指针也可做作为函数的参数。

void 类型指针

void 指针可以指向任意类型的数据,也就是说可以用任意类型的指针对 void 指针赋值;而如果要将 void 指针赋值给其它类型指针,则需要强制类型转换。

int *a;
void *vptr = a;
/* ... */
a = (int *)vptr;
  • 不允许对 void 指针进行算术操作,如 vptr + 1vptr ++
  • 使用 delete 释放 void 指针时需要显式类型转换,即 delete (type *)vptr
  • 可以将函数中的形参定义为 void 指针以接受任意指针类型的实参,但是这样在函数中便无法确定指针的实际类型;

指针与字符串

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

定义方式如下:

char *ptr = "Hello World!";
// 初始化定义指向字符常量会导致编译器警告
char str[] = "Hello World!";
// 将字符字面量拷贝给 str,str 的地址与字面量不同
char *ptr = str;

第一种定义方式编译通过的原因是,字符字面量实际存储在静态存储区,是持久存在的;因此也不能通过 ptr 间接修改字符串值。

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

char *str[4] = { "Hello", "World", "!" };
// 此时 str 使用上类似于二维数组

用指针遍历字符串:

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

References

C语言指针学习

菜鸟教程

OI Wiki

还有两篇学习时看的博文因为时间比较久找不到了QAQ,向两篇博客的作者表示sry。文章来源地址https://www.toymoban.com/news/detail-746319.html

到了这里,关于C++ 指针学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++中的智能指针是什么

    C++是一种广泛使用的编程语言,它允许程序员使用动态分配的内存。然而,手动管理内存可能会导致一些严重的问题,如内存泄漏和悬空指针。为了解决这些问题,C++引入了智能指针的概念。智能指针是一种特殊的指针类型,它可以自动管理内存并确保在不需要时释放内存。

    2024年02月02日
    浏览(119)
  • C++面试八股文:什么是智能指针?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第19面: 面试官:什么是智能指针? 二师兄:智能指针是C++11引入的类模板,用于管理资源,行为类似于指针,但不需要手动申请、释放资源,所以称为智能指针。 面试官:C++11引入了哪些智能指针? 二师兄:三种,分别是 s

    2024年02月09日
    浏览(44)
  • c++ 学习 之 this 指针的学习

    在c++ 中类内成员变量和成员函数分开存储 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码 那么问题来了,这一块代码是如何区分哪个对象调用自己呢? c++ 通过提供特殊的对象指针,this指针,解决上述问题, his指针指向被调用的成

    2024年02月08日
    浏览(36)
  • 【C++学习】智能指针

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! 如上图代码所示,在Func中开辟动态空间,在调用完Division函数后释放该空间。 如果Division没有抛异常,那么动态空间会被正常释放。 如果Division抛了异常,就会去匹配对应的catch,而Fu

    2024年02月06日
    浏览(57)
  • c++ 学习系列 -- 智能指针

    C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。但使用普通指针,容易造成内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等。 另外,使用普通指针容易产生 野指针、悬空指针 等问题。 所以 C++11 就引入了智能指

    2024年02月13日
    浏览(59)
  • 【C++学习】函数指针

    函数指针的应用场景,主要是用于回调,用个函数指针,调用方自己实现函数的内容,但调用方式和入参由自己定义。

    2024年02月10日
    浏览(34)
  • C++学习之旅 - 指针

    指针的基本概念 指针的作用: 可以通过指针间接访问内存 内存编号是从0开始记录的,一般用十六进制数字表示 可以利用指针变量保存地址 指针的定义与使用 指针的语法 数据类型* 指针变量名 指针占用的内存空间 我这里是8个字节的原因是我是64位电脑 你可以在 VSstudio 中更

    2024年02月08日
    浏览(41)
  • C++智能指针学习——小谈引用计数

    目录 前言 控制块简介 共享控制块 引用计数与弱引用计数创建过程 __shared_ptr __shared_count _Sp_counted_base 弱引用计数增加过程 再谈共享控制块 __weak_count 引用计数增加过程 弱引用计数的减少过程 弱引用计数减为0 引用计数的减少过程 引用计数减为0 参考文章 本文结合源码讨论

    2024年04月08日
    浏览(49)
  • C++笔记之基类指针动态地指向某一个子类情况列举

    code review!

    2024年02月12日
    浏览(49)
  • C++野指针(Wild Pointers)是什么?如何避免?如何正确地使用new和delete?

    C++野指针(Wild Pointers)是什么?如何避免? C++野指针(Wild Pointers)指的是那些指向无效内存地址的指针。野指针通常是由于内存管理不当导致的,比如未初始化的指针、指向已释放内存的指针、越界访问导致的指针等。野指针是非常危险的,因为它们可能引发未定义行为,

    2024年02月20日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包