C语言指针

 一、定义和声明指针

        指针是一个特殊的变量,它存储另一个变量的内存地址。指针的声明需要指定它所指向的数据类型,这样编译器就可以正确地进行指针算术和类型转换。

1、定义指针(分配内存空间,并确定指针的类型)

int *ptr;   // 定义一个指向整数的指针
char *cptr; // 定义一个指向字符的指针
这里,int *ptr 声明了一个指向 int 类型的指针,char *cptr 声明了一个指向 char 类型的指针。ptr 和 cptr 都是指针变量,它们存储了指向 int 和 char 类型数据的内存地址。

2、指针类型

指针类型决定了,指针在被解引用的时候所访问的权限,例如:
整型指针解引用会访问4个字节,字符指针解引用会访问1个字节

3、声明指针(告知编译器该指针的类型和存在,但不分配内存(当用 extern 关键字时))

4、定义与声明的结合

有时候,指针可能在一个地方定义,在其他地方声明。例如:
定义:
int *ptr;  // 定义
声明(在另一个文件中):
extern int *ptr;  // 声明
在这种情况下,ptr 在一个文件中定义,在另一个文件中声明,允许在不同的文件之间共享该指针。

二、指针的初始化

在使用指针之前,必须将其初始化为一个有效的地址。未初始化的指针会指向不确定的内存位置,可能导致程序崩溃或不确定的行为。
//-----------------------------------1.指针初始化为有效地址---------------------------------
int x = 10;
int *ptr = &x; // ptr 现在指向 x 的地址
//这里,&x 是取 x 的地址,ptr 现在存储了 x 的内存地址。通过 ptr,可以访问 x 的值。

//-----------------------------------2.指针初始化为NULL---------------------------------
int *ptr = NULL;
//在这种情况下,ptr 不指向任何有效的内存地址。使用 NULL 指针可以帮助避免对无效内存地址的操作。

//-----------------------------------3.动态分配内存---------------------------------
int *ptr = (int *)malloc(sizeof(int) * 10);  // 分配足够存储 10 个 int 的内存

三、野指针(指针指向位置不可知)

1、野指针的成因

    1.1 指针未初始化

int* p; // 未初始化指针 
*p = 10; // p 是一个未初始化的指针,可能指向任意的内存地址。对其进行解引用会导致未定义行为。

    1.2指针指向无效内存地址(局部变量、已释放的内存)

//-----------------------------------1.局部变量的地址---------------------------------
int* getPointerToLocal()
{
    int localVar = 10; // 局部变量
    return &localVar; // 返回局部变量的地址
}
int main()
{
    int* p = getPointerToLocal(); // p 现在是野指针
    printf("%d\n", *p); // 未定义行为
    return 0;
}
//-----------------------------------2.已释放的内存---------------------------------
int* allocateMemory() 
{ 
    int* p = (int*)malloc(sizeof(int)); 
    *p = 20; 
    return p; 
} 
int main() 
{ 
    int* p = allocateMemory(); 
    free(p); // 释放内存 
    *p = 30; // 使用已释放的内存(野指针) 
    printf("%d\n", *p); 
    return 0; 
}

      1.3指针的越界访问

int main() 
{ 
    int array[3] = {1, 2, 3}; 
    int* p = &array[3]; // 指向数组越界的位置, p指向了 array 的有效范围之外的位置,访问该位置会导致未定义行为
    *p = 10; // 未定义行为
    printf("%d\n", array[3]); // 未定义行为 
    return 0; 
}

2、规避野指针的方法

    ①指针要初始化
    ②要注意指针越界的问题
    ③指针所指向的空间及时置NULL
    ④避免返回局部变量的地址
    ⑤指针使用之前检查有效性

四、解引用指针

解引用指针是访问指针所指向的内存地址中的值。使用解引用运算符 * 可以实现这一操作。
int x = 10;
int *ptr = &x;
printf("%d\n", *ptr);  // 输出 10
*ptr 解引用 ptr,访问 ptr 指向的地址中的值。在这个例子中,*ptr 是 10,因为 ptr 指向 x。

五、指针运算

1. 加法和减法

   ptr + n:将指针 ptr 向前移动 n 个元素的位置。
   ptr - n:将指针 ptr 向后移动 n 个元素的位置。
int arr[] = {10, 20, 30, 40, 50}; 
int *ptr = arr;
printf("Original value: %d\n", *ptr); // 10 
printf("After adding 2: %d\n", *(ptr + 2)); // 30 
printf("After subtracting 1: %d\n", *(ptr + 1)); // 20

2. 指针之间的减法

   ptr1 - ptr2:计算两个指针 ptr1 和 ptr2 之间的元素差值,结果是它们之间相隔的元素个数。
int arr[] = {10, 20, 30, 40, 50}; 
int *ptr1 = &arr[1]; 
int *ptr2 = &arr[4]; 
printf("Difference: %d\n", ptr2 - ptr1); //3

3. 类型注意

  指针运算是基于指针所指向的数据类型的大小进行的。例如,如果 ptr 是 int 类型的指针,ptr + 1 实际上会增加 sizeof(int) 个字节的位置。

六、常量指针和指针常量

1. 常量指针(const int* 或 int const*)

1.1 定义:

    常量指针是指指针所指向的数据是常量的,不能通过这个指针修改所指向的值。
    指针本身可以改变,意味着可以指向不同的内存地址。

1.2 示例:

void example()
{
    int value1 = 10;
    int value2 = 20;

    const int* ptr = &value1; // 指向一个常量整型
    // *ptr = 15;  // 错误:无法通过 ptr 修改 value1
    printf("Value1: %d\n", *ptr); // 输出: Value1: 10

    ptr = &value2; // 合法:可以改变 ptr 指向的地址
    printf("Value2: %d\n", *ptr); // 输出: Value2: 20
}

1.3 关键点:

    const int* ptr 表示 ptr 是一个指向常量整数的指针,不能通过 ptr 修改 ptr 所指向的数据。
    但 ptr 本身可以被重新赋值,即它可以指向其他变量。

2. 指针常量(int* const)

1.1 定义:

    指针常量是指指针本身是常量的,不能改变指针的地址。
    指针所指向的数据可以被修改。

1.2 示例:

void example()
{
    int value1 = 10;
    int value2 = 20;

    int* const ptr = &value1; // 指针常量
    *ptr = 15; // 合法:可以修改 value1 的值
    printf("Value1: %d\n", *ptr); // 输出: Value1: 15

    // ptr = &value2;  // 错误:无法修改指针 ptr 指向的地址
}

1.3 关键点:

    int* const ptr 表示 ptr 是一个指针常量,指针本身是常量,不能改变其指向的地址。
    但通过 ptr 仍然可以修改 ptr 所指向的值。

3. 常量指针常量(const int* const)

1.1 定义:

    常量指针常量是指指针本身和指针所指向的数据都是常量。
    既不能修改指针的地址,也不能修改指针所指向的数据。

1.2示例:

void example()
{
    int value = 10;
    const int* const ptr = &value; // 常量指针常量
    // *ptr = 15;  // 错误:无法修改 value 的值
    // ptr = &anotherValue;  // 错误:无法修改 ptr 的地址
    printf("Value: %d\n", *ptr); // 输出: Value: 10
}

1.3 关键点:

    const int* const ptr 表示 ptr 既是常量指针(指针地址不可变),又是指向常量整数(数据值不可变)的指针。

4. 总结

常量指针(const int*):指针所指向的数据不可修改,但指针本身可以修改。
指针常量(int* const):指针本身不可修改,但指针所指向的数据可以修改。
常量指针常量(const int* const):指针本身和指针所指向的数据都不可修改。

七、指针数组和数组指针

1、指针数组 (Array of Pointers)

    1.1 定义:指针数组是一个数组,数组的每个元素都是指向某种类型的指针。
    1.2 内存布局:内存中,指针数组的每个元素都存储一个指针地址。
    1.3 用途:常用于存储指向不同内存位置的指针,如指向字符串的指针数组。
int a = 10, b = 20, c = 30;
int* ptrAr r[3] = { &a, &b, &c };  // int* arr[3] : 表示 arr 是一个包含 3 个 int* 类型元素的数组

2、数组指针 (Pointer to an Array)

    2.1 定义:数组指针是一个指针,它指向一个整个数组(而不是数组的单个元素)。
    2.2 内存布局:指针本身存储的是整个数组的地址,操作时可以直接访问数组的所有元素。
    2.3 用途:常用于需要通过指针操作整个数组的场景,比如传递固定大小的数组给函数。
int arr[3] = { 1, 2, 3 };
int (*arrPtr)[3] = &arr;  // int (*ptr)[3] : 表示 ptr 是一个指向包含 3 个 int 元素的数组的指针

八、一级指针和二级指针

1、一级指针传参

void test(int* ptr) {
    // 函数体
}
int main() {
    int arr[10] = {0}; // 声明一个包含10个 int 元素的数组
    int* p = arr;      // arr 被隐式转换为 int* 类型,并赋值给 p
    test(arr);         // arr 被隐式转换为 int* 类型,传递给 test 函数
    test(p);          // p 已经是 int* 类型,直接传递
    int a = 0;
    test(&a);          // &a 是 int* 类型,传递给 test 函数
    return 0;
}

2、二级指针传参

void test(int* ptr) {
    // 函数体
}
int main() {
    int a = 0;
    int* pa = &a;
    int** ppa = &pa;
    int* arr[5] = {0};      
    test(ppa);         
    test(&pa);        
    test(arr);          // 传入arr相当于传入数组首元素的地址,而首元素类型是int*,那么int*的地址就是int**
    return 0;
}

你可能感兴趣的:(C语言,c语言,开发语言)