import{_ as s,c as i,o as t,a6 as a}from"./chunks/framework.CZKtKhAm.js";const d="/c/assets/1.L8V3GBrc.png",l="/c/assets/2.CdvhiwcU.png",e="/c/assets/3.D74t3-Xt.png",y=JSON.parse('{"title":"第一章:颇具争议的指针","description":"","frontmatter":{},"headers":[],"relativePath":"notes/01_c-basic/06_xdx/index.md","filePath":"notes/01_c-basic/06_xdx/index.md","lastUpdated":1722496166000}'),n={name:"notes/01_c-basic/06_xdx/index.md"},o=a('

第一章:颇具争议的指针

1.1 概述

NOTE

之所以指针在 C 语言中颇具争议,是因为一方面其功能强大,直接操作内存地址;另一方面,又很危险,不正确的使用指针的方式,非常容易导致程序崩溃。

IMPORTANT

1.2 现代化高级编程语言是如何解决指针危险的?

IMPORTANT

总而言之,各种编程语言通过引入不同的策略和机制,如:智能指针、垃圾回收器、所有权和借用,以及强类型系统,有效地减少了指针操作所带来的各种安全性和可靠性问题,提升了程序的稳定性和开发效率。

第二章:回顾知识

2.1 变量

c
数据类型 变量名 = 值 ;

IMPORTANT

变量名(标识符)需要符合命名规则和命名规范!!!

2.2 普通变量和指针变量的区别

img

2.3 运算符

2.3.1 概述

img

NOTE

掌握一个运算符,需要关注以下几个方面:

IMPORTANT

普通变量支持上述的所有运算符;而指针变量并非支持上述的所有运算符,且支持运算符的含义和普通变量相差较大!!!

2.3.2 运算符的优先级

优先级运算符名称或含义结合方向
1[]数组下标➡️(从左到右)
()圆括号
.成员选择(对象)
->成员选择(指针)
2-负号运算符⬅️(从右到左)
(类型)强制类型转换
++自增运算符
--自减运算符
*取值运算符
&取地址运算符
!逻辑非运算符
~按位取反运算符
sizeof长度运算符
3/➡️(从左到右)
*
%余数(取模)
4+➡️(从左到右)
-
5<<左移➡️(从左到右)
>>右移
6>大于➡️(从左到右)
>=大于等于
<小于
<=小于等于
7==等于➡️(从左到右)
!=不等于
8&按位与➡️(从左到右)
9^按位异或➡️(从左到右)
10|按位或➡️(从左到右)
11&&逻辑与➡️(从左到右)
12||逻辑或➡️(从左到右)
13?:条件运算符⬅️(从右到左)
14=赋值运算符⬅️(从右到左)
/=除后赋值
*=乘后赋值
%=取模后赋值
+=加后赋值
-=减后赋值
<<=左移后赋值
>>=右移后赋值
&=按位与后赋值
^=按位异或后赋值
|=按位或后赋值
15,逗号运算符➡️(从左到右)

WARNING

IMPORTANT

第三章:指针的理解和定义(⭐)

4.1 概述

4.2 普通变量和指针变量的区别

在 CLion 中使用 GDB 调试时,可以通过反编译代码来查看指针变量和普通变量的区别。下面是具体的步骤:

设置 GDB 调试器

  1. 打开 CLion,并加载你的项目。
  2. 确保你使用的是带有 GDB 支持的工具链(如 GCC 工具链)。
  3. 在 CLion 的设置中,确保调试器设置为 GDB。

编译你的代码

确保在编译你的代码时使用了调试信息生成选项(如 -g)。你可以在 CMakeLists.txt 文件中添加以下行:

cmake
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -g")

开始调试

  1. 设置断点:在代码中你想要查看变量的地方设置一个断点。
  2. 启动调试:点击调试按钮启动调试会话。

查看变量

当调试器在断点处暂停时,你可以在调试控制台中使用 GDB 命令来查看变量。以下是一些常用的 GDB 命令:

区分指针变量和普通变量

指针变量和普通变量的主要区别在于它们的类型和存储的内容。指针变量存储的是地址,而普通变量存储的是实际的值。通过 GDB 命令可以很容易地看到这种区别。

示例

假设有如下代码:

cpp
#include <iostream>

int main() {
    int a = 10;
    int *p = &a;
    std::cout << "a: " << a << ", p: " << p << std::endl;
    return 0;
}

在 CLion 中设置断点并开始调试,程序将在 std::cout 行暂停。此时在调试控制台中输入以下命令:

通过这些命令,你可以看到指针变量 p 实际上存储的是一个地址,而普通变量 a 存储的是一个整数值。

使用反汇编

在某些情况下,你可能需要查看反汇编代码来更深入地理解变量的存储方式。使用以下命令可以查看当前函数的反汇编代码:

总结

在 CLion 中使用 GDB 调试时,通过查看变量值和反汇编代码,可以清楚地区分指针变量和普通变量。指针变量存储地址,而普通变量存储实际的值,通过适当的 GDB 命令可以轻松辨别两者的区别。

第五章:指针的运算(⭐)

5.1 概述

5.2 总结

WARNING

在使用指针时,务必小心避免野指针和内存泄漏等问题。

在C语言中,同类指针相减的结果是一个整数,它表示两个指针之间相隔多少个指向的对象单位,而不是它们在内存中的字节偏移量。这种对象单位是指针所指向的具体类型的大小。

举个例子来说,如果你有两个指向整数数组元素的指针 pq,那么 p - q 的结果将是 p 指向的数组元素的索引与 q 指向的数组元素索引之间的差值。这个差值代表了在数组中相隔多少个整数元素,而不是它们在内存中的字节偏移量。

这种设计的优势在于,它使得指针运算更加直观和便于理解,特别是在处理数组和其他连续存储的数据结构时。因为指针运算结果的单位是根据指针所指向的具体类型来计算的,这样可以确保不同平台上的程序行为是一致的,不会受到底层硬件架构或者字节对齐规则的影响。

在C语言中,数组名和指针有很多相似之处,但数组名并不是指针变量。数组名实际是一个常量,它指向数组的第一个元素的地址。为了证明这一点,可以通过以下几个方面来说明:

  1. 数组名表示数组首地址: 数组名可以作为一个指针使用,数组名本身表示的是数组首地址。

    c
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr; // 这句话是合法的,ptr现在指向arr[0]
    printf("%p\\n", arr);  // 打印数组名,会打印数组首地址
    printf("%p\\n", &arr[0]);  // 打印第一个元素的地址
  2. 数组名是常量指针: 数组名是一个常量指针,不能改变它指向的位置,而指针变量可以改变它指向的位置。

    c
    int arr[5];
    int *ptr = arr;  // 合法,ptr指向arr[0]
    ptr++;  // 合法,ptr现在指向arr[1]
    
    // arr++;  // 非法,编译错误,因为数组名是常量,不能改变
  3. sizeof运算符的结果不同: 使用sizeof运算符对数组名和指针变量会得到不同的结果,数组名会返回整个数组的大小,而指针变量会返回指针本身的大小。

    c
    int arr[5];
    int *ptr = arr;
    
    printf("sizeof(arr) = %lu\\n", sizeof(arr));  // 返回数组的大小,5 * sizeof(int)
    printf("sizeof(ptr) = %lu\\n", sizeof(ptr));  // 返回指针的大小,通常是4或8字节
  4. 地址运算符的结果不同: 使用地址运算符&对数组名和指针变量会得到不同的结果,对数组名使用&会返回数组的地址,而对指针变量使用&会返回指针变量本身的地址。

    c
    int arr[5];
    int *ptr = arr;
    
    printf("Address of array: %p\\n", &arr);  // 返回整个数组的地址
    printf("Address of pointer: %p\\n", &ptr);  // 返回指针变量ptr的地址

综上所述,通过这些示例和解释,可以看出数组名虽然在某些场合下可以像指针一样使用,但它并不是一个真正的指针变量,而是一个常量,表示数组的首地址。

`,73),p=[o];function r(h,c,k,g,u,E){return t(),i("div",null,p)}const m=s(n,[["render",r]]);export{y as __pageData,m as default};