C语言——指针_学习笔记

本文是初步认识C语言指针,深入解析C语言指针,欢迎阅读下一篇文章C语言——深入理解指针_学习笔记

一、指针是什么

基本概念和相关属性

1.1指针定义

指针就是计算机内存中字节的编号,就是计算机内存中字节的地址,简单来说,指针就是地址,地址就是指针。

1.2指针的大小

前面说了,指针表示内存中的地址,而内存地址需要与机器字长相匹配。所以,指针的大小与机器字长有关,和类型是无关的,只要是指针变量,在相同的平台下,大小都是相同的。

在32位计算机上,指针的大小是4字节(32位),而在64位计算机上,指针的大小是8字节(64位)。

1字节 == 8比特位,
32个比特位 == 4字节
64个比特位 == 8字节

二、为什么要用指针

指针在C语言使用过程中有什么意义

2.1指针的意义

  1. 指针的意义在于通过地址直接访问内存,提高程序的效率
  2. 指针的意义在于有些时候不方便直接访问想要的数据,这时候可以借助指针来进行间接访问

2.2指针的好处

  1. 指针提供了灵活性:C语言使用指针可以方便地修改和传递变量的地址,以及实现动态内存分配和数据结构的灵活操作。指针可以用于操作数组、字符串、函数参数等,使程序的逻辑更加清晰、代码更加简洁。
  2. 指针提供了高效的内存访问:通过指针可以访问变量的地址,直接对内存进行操作,从而提高程序的执行效率。同时,指针也可以用于优化内存管理,例如通过指向指针的指针实现多维数组的访问。

三、指针怎么用

指针的使用和相关注意事项

3.1 和指针有关的操作符

符号和名称 使用方法
&:取地址操作符 用于获取变量的内存地址。例如,int a = 10; int *p = &a;,这里&a就是获取变量a的内存地址。
*:指针访问操作符 用于访问指针所指向的内存地址的值。例如,int b = * p;,这里*p就是访问指针p所指向的内存地址的值,并将其赋值给变量b。
++:指针自增操作符 它用于将指针向后移动一个元素的大小。例如,int *p = &a; p++;,这里p++就是将指针p向后移动一个元素的大小。
- -:指针自减操作符 用于将指针向前移动一个元素的大小。例如,int *p = &a; p- -;,这里p- -就是将指针p向前移动一个元素的大小。
- >:指针访问成员操作符 用于通过指针访问结构体或类的成员。例如,struct Person { int age; }; struct Person p = { 20 }; int *p_age = &p.age; int age = p_age->age;,这里p_age->age就是通过指针p_age访问结构体p的成员age。

3.2 指针的定义和初始化

  1. 定义指针变量
<data_type>* <variable_name>;
//*号,左边是指向数据的类型,右边是指针变量的名字

例如:

int* ptr;
//一个指向int 类型的指针变量,名字叫ptr
  1. 初始化指针变量
<data_type>* <variable_name> = <address>;
//定义后面用赋值运算符传给他一个地址

例如:

int x = 10;
int* ptr = &x;

这里,ptr被初始化为变量x的地址。

也可以在定义指针变量的同时进行初始化:

int x = 10;
int* ptr = &x;

3.3 指针的运算

1. 指针加法 / 减法

指针加法运算可以用于计算指针之间的相对距离,或者将指针向后或向前移动一定的元素个数。

#include <stdio.h>

int main() {
    
    
    int arr[5] = {
    
    1, 2, 3, 4, 5};
    int* ptr1 = arr;
    int* ptr2 = ptr1 + 2;

    printf("ptr1: %p\n", ptr1);
    printf("ptr2: %p\n", ptr2);

    printf("Value pointed by ptr1: %d\n", *ptr1);
    printf("Value pointed by ptr2: %d\n", *ptr2);

    return 0;
}

在这个例子中,我们定义了一个包含5个元素的整数数组arr,并将ptr1初始化为数组的第一个元素的地址。然后,我们通过将ptr1加上2,得到新的指针ptr2,它指向数组的第三个元素。最后,我们使用printf函数输出指针的地址,以及它们所指向的值。

输出结果:

ptr1: 0x7fff5fbff664
ptr2: 0x7fff5fbff668
Value pointed by ptr1: 1
Value pointed by ptr2: 3

可以看到,ptr2的地址比ptr1的地址多了4(对应两个整数的内存大小),并且它们所指向的值分别是1和3,即数组的第一个和第三个元素。这个例子展示了指针加法运算的用法,可以用于计算指针之间的相对距离,或者将指针向后或向前移动一定的元素个数。

注意:指针+1到底加几个字节,取决于指针类型!!并不是说指针+1就一定跳过一个字节!!

2. 指针自增 / 自减

指针的自增和自减运算可以用于将指针向前或向后移动一个元素的位置。

例如:

#include <stdio.h>

int main() {
    
    
    int arr[5] = {
    
    1, 2, 3, 4, 5};
    int* ptr = arr;

    printf("Value of *ptr: %d\n", *ptr);
    ptr++;
    printf("Value of *ptr after increment: %d\n", *ptr);

    return 0;
}

在这个例子中,我们定义了一个包含5个元素的整数数组arr,并将ptr初始化为数组的第一个元素的地址。然后,我们使用指针自增运算将ptr向后移动一个元素的位置,也就是指向数组的第二个元素。接下来,我们使用printf函数输出ptr所指向的值。

输出结果如下:

Value of *ptr: 1
Value of *ptr after increment: 2

可以看到,指针自增运算使得ptr向后移动了一个元素的位置,并且新的指针所指向的值也发生了变化。

3.4 const修饰指针

const修饰指针的目的是提供一种安全机制来防止通过指针来修改所指向的数据,同时也可以限制指针变量的使用方式。

  1. const 位于* 号左边,修饰的是指针
    意味着你不能通过const指针来修改所指向的对象的值。但是可以改变指针的指向。
const int a = 10;
const int *ptr = &a;

//如果 *ptr=20; 则会报错
//如果 int b = 20;
//     ptr = &b; 则不报错
  1. const 位于* 号右边,修饰的是指针变量名
    表示这个指针是一个常量指针,不能被赋值为其他地址,不能改变指针的指向。但是可以改变指针指向的对象的值。
int a = 10;
int * const ptr = &a;

//如果  *ptr=20; 则a的值变成20
//如果  int b = 20;
//      ptr = &b; 则会报错
  1. *号两边都有const 修饰
    意味着,你既不能改变指针的指向,也不能改变指针所指向对象的值。
int a = 10;
const int * const ptr = &a;

//如果  *ptr=20; 会报错
//如果  int b = 20;
//      ptr = &b; 也会报错

3.5 一些常见的指针

1. 指向常量的指针

#include <stdio.h>

int main() {
    
    
    const int k = 10;
    int *p = &k; // 指针p指向常量k

    printf("k = %d\n", k); // 输出k的值
    *p = 20; // 试图通过指针修改常量的值,无效
    printf("k = %d\n", k); // 再次输出k的值,仍然是10

    return 0;
}

常量不能修改,用const 修饰。

2. 指向变量的指针

#include <stdio.h>

int main() {
    
    
    int x = 10;
    int *p = &x; // 指针p指向变量x

    printf("x = %d\n", x); // 输出x的值
    *p = 20; // 通过指针修改变量的值
    printf("x = %d\n", x); // 再次输出x的值,现在是20

    return 0;
}

这个也是指针比较常见的使用场景,借助指针变量 p 修改变量 x的值。

3. 指向数组的指针

指向数组的指针通常用于操作数组元素,例如遍历数组、访问数组元素等。

例如:

#include <stdio.h>

int main() {
    
    
    int arr[] = {
    
    1, 2, 3, 4, 5};
    int *ptr = arr; // 指向数组的指针

    // 遍历数组并输出元素值
    for (int i = 0; i < 5; i++) {
    
    
        printf("%d ", *(ptr + i));
    }
    printf("\n");

    // 修改数组元素的值
    *(ptr + 2) = 10; // 将数组的第3个元素修改为10
    printf("%d %d %d %d %d\n", arr[0], arr[1], arr[2], arr[3], arr[4]);

    return 0;
}

在上面的例子中,我们定义了一个整型数组arr,然后定义了一个指向数组的指针ptr,并将其初始化为数组的首地址。通过指针ptr,我们可以直接访问数组的元素,例如使用*(ptr + i)来访问数组的第i个元素。同时,我们也可以通过指针修改数组元素的值,例如使用*(ptr + 2)来修改数组的第3个元素。

4. 指向函数的指针

指向函数的指针可以用于调用函数或者将函数作为参数传递。

例如:

#include <stdio.h>

void myFunction(int x) {
    
    
    printf("The value of x is: %d\n", x);
}

int main() {
    
    
    void (*funcPtr)(int); // 定义指向函数的指针
    funcPtr = &myFunction; // 将指针指向myFunction函数
    (*funcPtr)(5); // 通过指针调用函数,输出"The value of x is: 5"
    return 0;
}

PS:得到一个函数的地址,可以采用 ptr = &(函数名),也可以 ptr = 函数名,因为对于函数来说, &(函数名),函数名都是函数的地址

在上面的例子中,我们定义了一个函数myFunction,它接受一个整数参数并输出该参数的值。然后,我们定义了一个指向函数的指针funcPtr,它指向了myFunction函数。最后,我们通过指针funcPtr调用myFunction函数,并将参数值设置为5。输出结果为"The value of x is: 5"。

5. 指向结构体的指针

指向结构体的指针通常用于操作结构体变量,例如访问结构体成员、修改结构体变量等。

例如:

#include <stdio.h>

struct Person {
    
    
    char name[50];
    int age;
};

int main() {
    
    
    struct Person person1 = {
    
    "Alice", 25};
    struct Person *ptr = &person1; // 指向结构体的指针

    printf("Name: %s\n", ptr->name); // 通过指针访问结构体成员
    printf("Age: %d\n", ptr->age);

    ptr->age = 30; // 通过指针修改结构体成员的值
    printf("Updated Age: %d\n", ptr->age);

    return 0;
}

在上面的例子中,我们定义了一个Person结构体,它包含一个字符串成员name和一个整数成员age。然后,我们定义了一个指向Person结构体的指针ptr,并将其初始化为person1的地址。通过指针ptr,我们可以访问结构体成员,例如使用ptr->name访问name成员,使用ptr->age访问age成员。同时,我们也可以通过指针修改结构体成员的值,例如使用ptr->age = 30将age成员的值修改为30。最后,我们输出了更新后的age成员的值。

6. 指向指针的指针(二级指针)

指向指针的指针是指向指针变量的指针,通常用于操作指针变量,例如修改指针变量的值等。

例如:

#include <stdio.h>

int main() {
    
    
    int a = 10;
    int *p = &a; // 指向变量a的指针
    int **pp = &p; // 指向指针p的指针

    printf("a = %d\n", a);
    printf("*p = %d\n", *p);
    printf("**pp = %d\n", **pp);

    *pp = NULL; // 将指针p设置为NULL
    printf("p = %p\n", p); // 输出NULL

    return 0;
}

在上面的例子中,我们定义了一个整数变量a,然后定义了一个指向a的指针p,以及一个指向指针p的指针pp。通过pp我们可以访问p的值,例如使用**pp访问a的值。同时,我们也可以通过pp修改p的值,例如使用*pp = NULL将p设置为NULL。最后,我们输出了p的值,结果为NULL。

3.6 指针使用的注意事项

  1. 指针变量必须先初始化,否则指针变量没有指向任何有效的内存地址,会导致未定义行为。
  2. 指针变量指向的内存地址必须是合法的,不能指向已经被释放的内存或者无效的内存地址,否则会导致未定义行为。
  3. 指针变量不能指向一个不具有该类型的值的内存地址,否则会导致未定义行为。
  4. 指针变量不能进行算术运算,例如不能将两个指针变量相加,否则会导致未定义行为。
  5. 指针变量可以指向数组元素,但是不能跨越数组元素之间的内存地址,否则会导致未定义行为。
  6. 指针变量可以指向指针变量,但是需要特别注意指针链的问题,不要越界访问或者修改指针链的值,否则会导致未定义行为。
  7. 在使用指针变量时,需要注意避免空指针访问和野指针问题,否则会导致未定义行为。
  8. 在使用指针变量时,需要注意内存泄漏问题,不要忘记释放已经分配的内存,否则会导致内存泄漏问题。

在使用C语言指针时需要特别注意内存管理和指针操作的问题,确保指针变量的合法性和正确性。

四、void* 指针

void* 指针是 C 语言中的一种特殊类型的指针。它表示一个指向未知数据类型的指针。换句话说,void* 指针可以指向任何类型的数据。

但是它不能被直接解引用,因为它没有指明具体的类型。它通常用于函数的参数或返回值,以便在函数内部处理任意类型的数据。

例如:

#include <stdio.h>

void print_int(void* ptr) {
    
    
    int value = *((int*) ptr);
    printf("%d\n", value);
}

int main() {
    
    
    int x = 42;
    print_int(&x);
    return 0;
}

在这个示例中,print_int() 函数接受一个 void* 指针作为参数,并将其转换为 int* 指针,然后解引用它以获取整数值并打印出来。

PS(注意):void*指针不能直接解引用,要强制类型转换

深入解析C语言指针,欢迎阅读下一篇文章C语言——深入理解指针_学习笔记

Guess you like

Origin blog.csdn.net/yjagks/article/details/132645515