C++ 指针学习笔记

用户云卷云舒  金牌会员 | 2023-12-28 15:08:51 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

C++ 指针学习笔记

引入

指针是什么

指针是一个变量,其值为另一个变量的地址。
指针声明的一般形式为:
  1. type *ptr_name;
复制代码
type 是指针的基类型,ptr_name 是指针的名称,* 用来指定一个变量是指针
对于一个指针,需要明确四个方面的内容:指针的类型指针所指向的类型指针的值指针所指向的内存区)、指针本身所占据的内存区
指针的类型

从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型:
  1. int *ptr; // int*
  2. char *ptr; // char*
  3. int **ptr; // int**
  4. int (*ptr)[3]; // int(*)[3]
  5. int *(*ptr)[4]; // int*(*)[4]
复制代码
指向类型


  • 所指向对象的类型
指针所指向对象的类型一般被称为 指针的类型
从语法上看,只须把指针声明语句中的指针名字和名字左边的指针声明符 * 去掉,剩下的就是指针所指向的类型:
  1. int *ptr; // int
  2. char *ptr; // char
  3. int **ptr; // int*
  4. int (*ptr)[3]; //int ()[3]
  5. int *(*ptr)[4]; //int *()[4]
复制代码

  • 间接访问
在用指针间接访问时,指针所指向的类型决定了编译器如何看待那片内存区中的内容:
  1. char *ptr = &a;
  2. // 假设 p 指向的地址为 4000,那么编译器默认 ptr 指向的是 4000 这一个字节的内容
  3. int *ptr = &a;
  4. // 编译器会认为 ptr 所指向的对象是由 4000, 4001, 4002, 4003 四个字节共同组成
  5. double *ptr = &a;
  6. // 编译器会认为 ptr 所指向的对象是由 4000 ~ 4007 八个字节共同组成
复制代码

  • 指针的算术运算
指针的类型影响指针的算术运算,如对于 p + 1 ,系统会将指针 p 的值加上 sizeof(type)
指针的值(指针所指向的内存区或地址)

指针的值是指针本身存储的数值,这个值被编译器视作一个地址。
指针所指向的内存区以指针的值为起始地址,长度为 sizeof(type) 的一片内存区。
指针本身所占据的内存区

字节长度为 sizeof(p)
指针的定义

初始化和赋值

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

  • 0 及 NULL
  • 相同指向类型的对象的地址
空指针:NULL 和 nullptr

NULL 指针是一个定义在标准库中的值为 0 的常量。
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为 空指针
  1. int *ptr = NULL;
  2. // or C++11 前
  3. int *ptr = 0;
复制代码
需要注意的是,在 C++11 之前,C++ 和 C 一样使用 NULL 宏表示空指针常量,C++ 中 NULL 的实现一般如下:
  1. // C++11 前
  2. #define NULL 0
复制代码
但是空指针和整数 0 的混用会导致很多问题,比如:
  1. int myFunction(int var);
  2. int myFunction(int *ptr);
复制代码
在调用 myfunction(NULL) 时,实际调用的函数类型是 int(int) 而不是 int(int *)。
C++11 中引入 nullptr 关键字作为空指针常量,且规定 nullptr 可以隐式转换为任何指针类型,转换结果为该结果的空指针值。
nullptr 的类型为 std::nullptr_t,称为空指针类型,可能实现如下:
  1. namespace std {
  2. typedef decltype(nullptr) nullptr_t;
  3. }
复制代码
另外,C++11 其 NULL 的宏定义也修改为:
  1. // C++11 起
  2. #define NULL nullptr
复制代码
C 语言中,C23 也引入了 nullptr 作为空指针常量,同时引入 nullptr_t 作为其类型。
指针的 const 限定

指向只读型对象的指针必须有 const 限定,称为 指向 const 对象的指针,定义形式为:
  1. const type *ptr_name;
复制代码

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

只读型指针(或 常量指针)的定义为:
  1. type* const ptr_name;
复制代码
需要注意的是 const 放在变量名前,与指向 const 对象的指针定义形式不同。
不能改变只读型指针的值,但是可以通过只读型指针间接修改指向对象
指针的运算

指针的算术运算

指针的四种算术运算:+, -, ++, --
指针算术运算会根据指针所指向的类型和大小来决定移动的距离,即
执行 ptr++ 或 ptr-- 后,指针 ptr 的值会增加 sizeof(type),指向下一个该类型元素的地址。
指针的比较

指针可以用关系运算符 ==,  进行比较。
两个相比较的指针所指向的类型应当相同,即指向相关变量。
指针与指针之间的减法运算

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

数组与指针

C++ 规定数组名即代表 数组本身,又代表 整个数组的地址,还是 数组首元素的地址。即,声明一个数组
  1. type array[N];
复制代码
那么数组名 array 就有了两重含义:

  • array 代表整个数组,类型为 type[N];
  • array 是一个 常量指针,类型为 type*,指向内存区为数组首元素。
    注意该指针占有单独的内存区,与数组首元素占据的内存区不同。
不同表达式中数组名有不同含义:
  1. sizeof(array); // 代表数组本身,sizeof(array) 为数组定义的字节 (bytes) 数,即 N * sizeof(type)
  2. *arrray; // 代表指针,值为 array[0] 即首元素的值
  3. sizeof(*array); // 值为数组单元的大小,即 sizeof(type)
  4. array + k; // 代表指针,类型为 type*,指向的类型为 type 即数组的第 n 个元素
  5. sizeof(array + k); // 值为指针类型的大小,即 sizeof(type*)
复制代码
指针与一维数组

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

定义指向一维数组的指针变量时,指向类型应与数组元素类型一致:
  1. int array[N];
  2. int *ptr = &array;
  3. int *ptr = array;
  4. int *ptr = *array[0];
  5. // 根据数组名的性质,以上三种定义防止等价,指针 ptr 的值都为数组首元素的地址
  6. int *ptr = *array[k];
  7. // 也可以将数组某一元素的地址赋值给指针
复制代码
一维数组的访问方式

由于数组的元素地址是规律性增加的(连续的),根据指针算术运算规律,可以利用指针及其算术运算来访问数组元素。
参照 定义 中的声明,以下访问 array 的方式等价:

  • 数组下标法:array
  • 指针下标法:ptr (指针 ptr 的值为数组首元素的地址,此时 ptr 和数组名等价)
  • 地址引用法:*(array + i)
  • 指针引用法:*(ptr + i)
定义 int array[N], *ptr = array;,需注意 ptr 和数组名 array 并不完全等价。
比如由于 array 是一个常量指针,所以不允许修改,如 array ++ 是不正确的语法。
遍历一维数组

首先定义 int array[N];
以下为遍历一维数组的几种方式:

  • 下标法:
    1. for (int i = 0; i < N; ++ i)
    2.     array[i];
    复制代码
  • 通过地址间接访问:
    1. for (int i = 0; i < N; ++ i)
    2.     *(array + i)
    复制代码
    以上两种方法似乎等价(?),笔者并未严格考究。
  • 指针访问法:
    1. for (int *p = array; p < a + N; ++ p)
    2.     *p;
    复制代码
    用指针作为循环变量(利用了指针的比较),优点是指针直接指向元素,无须重新计算地址,能提高运行效率。
    以下访问方式与前两种等价
    1. int *p = array;
    2. for (int i = 0; i < N; ++ i)
    3.     *(p + i);
    复制代码
指针与二维数组

数组的数组

定义二维数组
  1. int array[N][M];
复制代码
那么,数组名 array 代表二维数组首元素的起始地址,注意 首元素 并非整型,而是由 N 个整型元素构成的一维数组。即一下表达等价:
  1. array;
  2. array[0];
  3. array + 0;
  4. *array;
复制代码
此时 a[k] 即是一维数组的数组名。
定义(指向二维数组的指针)

二维数组定义如上。
  1. int (*ptr)[M] = array;
复制代码
注意此时 ptr 指向的是二维数组中的首元素(一维数组 array[0]),ptr ++ 则是将 ptr 指向 array[1](而不是 array[0][1])。
二维数组的访问方式

二维数及指针定义如上,以下访问方式等价:
  1. a[i][j];
  2. *(a[i] + j);
  3. *(*(a + i) + j);
  4. *(*(ptr + i) + j);
复制代码
指针数组

声明方式为:
  1. type *ptr[N];
  2. // 在 C++ 中,[] 的优先级高于 *,所以 ptr 会先与 [] 结合;
  3. // 此处 ptr 为一个数组,类型为 type **,数组的元素为类型为 type* 的指针
  4. type *(ptr[N]);
  5. // 两种写法等价
复制代码
指向指针的指针(多级间接寻址)

声明方式为:
  1. type **ptr_name;
复制代码
定义以下二级指针:
  1. int var;
  2. int *ptr = &var;
  3. int **pptr = &ptr;
复制代码
此时 pptr 的值为 ptr 的地址,如需通过 pptr 间接访问 var,则需要两个星号,即 **pptr。
指针与结构类型

可以声明一个指向结构类型对象的指针:
  1. struct Struct_name {
  2.     member_type1 member_name1;
  3.     member_type2 member_name2;
  4.     member_type3 member_name3;
  5. } object_name;
  6. struct Struct_name *ptr = &object_name;
复制代码
访问成员变量的方式如下:

    1. ptr -> member_name1;
    复制代码
    1. (*ptr).member_name1;
    复制代码
    1. *ptr;
    2. *(ptr + k);
    复制代码
    注意第三种访问方式是不正规的,这是因为某些情况下,结构对象的相邻成员变量间可能会有若干“填充字符”(如字对齐、双字对齐等,即成员变量的内存地址不连续)。
指针与函数

传递指针给函数

C++ 允许传递指针给函数,只需要声明函数参数为指针。
传递指针给函数,大致有如下用法:

  • 函数内通过指针间接访问或修改指针所指向的对象;
  • 通过 const 限定的指针防止函数内修改指针所指向的对象;
  • 传递数组给函数,如
    1. int myFunction(int *arr) {
    2.     for (int i = 1; i < N; ++ i)
    3.         arr[i];
    4.            // 此时 arr 就是指向一维数组的指针
    5. }
    6. int main() {
    7.     int arr[N];
    8.     myFuntion(arr);
    9.     return 0;
    10. }
    复制代码
从函数返回指针

C++ 允许从函数返回指针,需要声明函数的返回值为指针类型:
  1. int * myFuntion() {
  2.         //   
  3. }
复制代码
注意,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
以下程序生成 N 个随机数,并返回表示指针的数组名(数组首元素的地址):
  1. #include <iostream>
  2. #include <ctime>
  3. #include <cstdlib>
  4. using namespace std;
  5. #define N 10
  6. int * getRandom() {
  7.     static int r[N];
  8.     // 注意不能在函数外返回局部变量的地址,所以需要定义 r 为 static 变量
  9.     srand((unsigned)time(NULL));
  10.     for (int i = 0; i < N; ++ i)
  11.         r[i] = rand();
  12.            return r;
  13. }
  14. int main() {
  15.     int *ptr = getRandom();
  16.     return 0;
  17. }
复制代码
指向函数的指针

函数指针 的声明如下:
  1. int myFuntion(char *, int) {}
  2. int (*ptr)(char *, int);
  3. int var = *ptr("Hello", k);
  4. // 通过函数指针调用函数
复制代码
可以通过函数指针实现 动态 调用函数,同时函数指针也可做作为函数的参数。
指针与字符串

通过字符型指针来处理字符串,其过程与通过指针访问数组元素相同。
定义方式如下:
  1. char *ptr = "Hello World!";
  2. // 初始化定义指向字符常量回导致编译器警告
  3. char str = "Hello World!";
  4. char *ptr = str;
复制代码
第一种定义方式编译通过的原因是,字符字面量实际存储在静态存储区,是持久存在的。
也可以定义一个指针数组存储多个字符串:
  1. char *str[4] = { "Hello", "World", "!" };
复制代码
用指针遍历字符串:
[code]char str[] = "Hello World!", *ptr = str;while (*p) cout
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户云卷云舒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表