import{_ as s,c as a,o as i,a6 as n}from"./chunks/framework.CZKtKhAm.js";const t="/c/assets/1.L8V3GBrc.png",l="/c/assets/2.CdvhiwcU.png",e="/c/assets/3.D74t3-Xt.png",d="/c/assets/4.DCQsqYYC.gif",p="/c/assets/5.CUjMoXNF.gif",r="/c/assets/6.BWbajkuB.gif",c="/c/assets/7.C1JnBDr7.png",o="/c/assets/8.DwN2p3bo.png",h="/c/assets/9.DdSfz-Pf.gif",k="/c/assets/10.BMocjVt7.gif",B=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":1722388783000}'),u={name:"notes/01_c-basic/06_xdx/index.md"},g=n('

第一章:颇具争议的指针

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

第三章:环境切换为 WSL(⭐)

3.1 概述

NOTE

win 11 中的 WSL2 就是个 Linux 环境,我们可以在上面跑各种 Linux 工具,这样我们就不需要再安装虚拟机软件了,如:VMware Workstation(它会完整的模拟一个硬件系统,并在上面跑各种 Linux ,实在是太笨重了)。

3.2 使用步骤

shell
dnf -y upgrade && dnf -y install valgrind # AlmaLinux
shell
apt -y update && apt -y upgrade && apt -y install valgrind # Ubuntu

shell
which valgrind

shell
cmake --version

cmake_minimum_required(VERSION 3.26.5) # 3.26.5

# 项目名称和版本号
project(c-study VERSION 1.0 LANGUAGES C)

# 设置 C 标准
set(CMAKE_C_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED True)

# 辅助函数,用于递归查找所有源文件
function(collect_sources result dir)
    file(GLOB_RECURSE new_sources "\${dir}/*.c")
    set(\${result} \${\${result}} \${new_sources} PARENT_SCOPE)
endfunction()

# 查找顶层 include 目录(如果存在)
if (EXISTS "\${CMAKE_SOURCE_DIR}/include")
    include_directories(\${CMAKE_SOURCE_DIR}/include)
endif ()

# 查找所有源文件
set(SOURCES)
collect_sources(SOURCES \${CMAKE_SOURCE_DIR})

# 用于存储已经处理过的可执行文件名,防止重复
set(EXECUTABLE_NAMES)

# 创建可执行文件
foreach (SOURCE \${SOURCES})
    # 获取文件的相对路径
    file(RELATIVE_PATH REL_PATH \${CMAKE_SOURCE_DIR} \${SOURCE})
    # 将路径中的斜杠替换为下划线,生成唯一的可执行文件名
    string(REPLACE "/" "_" EXECUTABLE_NAME \${REL_PATH})
    string(REPLACE "\\\\" "_" EXECUTABLE_NAME \${EXECUTABLE_NAME})
    string(REPLACE "." "_" EXECUTABLE_NAME \${EXECUTABLE_NAME})

    # 处理与 CMakeLists.txt 文件同名的问题
    if (\${EXECUTABLE_NAME} STREQUAL "CMakeLists_txt")
        set(EXECUTABLE_NAME "\${EXECUTABLE_NAME}_exec")
    endif ()

    # 检查是否已经创建过同名的可执行文件
    if (NOT EXECUTABLE_NAME IN_LIST EXECUTABLE_NAMES)
        list(APPEND EXECUTABLE_NAMES \${EXECUTABLE_NAME})

        # 创建可执行文件
        add_executable(\${EXECUTABLE_NAME} \${SOURCE})

        # 查找源文件所在的目录,并添加为包含目录(头文件可能在同一目录下)
        get_filename_component(DIR \${SOURCE} DIRECTORY)
        target_include_directories(\${EXECUTABLE_NAME} PRIVATE \${DIR})

        # 检查并添加子目录中的 include 目录(如果存在)
        if (EXISTS "\${DIR}/include")
            target_include_directories(\${EXECUTABLE_NAME} PRIVATE \${DIR}/include)
        endif ()

        # 检查并添加 module 目录中的所有 C 文件(如果存在)
        if (EXISTS "\${DIR}/module")
            file(GLOB_RECURSE MODULE_SOURCES "\${DIR}/module/*.c")
            target_sources(\${EXECUTABLE_NAME} PRIVATE \${MODULE_SOURCES})
        endif ()
    endif ()
endforeach ()

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

4.1 概述

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

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的地址

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

`,72),E=[g];function b(m,C,y,F,_,A){return i(),a("div",null,E)}const q=s(u,[["render",b]]);export{B as __pageData,q as default};