C语言学习笔记-指针01

指针是 C 语言的灵魂,现在对于指针的掌握肯定是不透彻的,然学习是一个迭代的过程,姑且写出目前自己的理解。

指针的概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main()
{
    int a = 10;
    int *p = NULL;
    
    p = (int *)malloc(sizeof(int));
    p = &a;

    printf("%d
", p);
    printf("%d
", *p);

    if(p != NULL)
    {
        free(p);
        p = NULL;
    }
}

先来看这样一段代码

  1. p 是一个指针变量,*p 是指针指向的内存空间的保存的值。
  2. &a 表示取变量 a 所在内存的地址,p = &a 表示将内存 a 所在的地址值赋值给 p,相当于指针 p 指向 a 所在的内存。
  3. 接下来分别打印 p 和 *p 来验证上面的说法
  4. 最后我们释放内存空间

明确几个概念:

  1. 指针是一个变量
  2. 注意区分指针变量和它指向的内存块
    1. 改变指针变量的值不会影响指针指向的内存区域
    2. 改变指针指向的内存区域也不会改变指针变量的值

内存四区

先来看这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char *get_str()
{
    char str[] = "abcdefg";
    return str;
}

int main()
{
	char buf[128] = {0};
    strcpy(buf, get_str());
    printf("buf = %s
", buf);
    
    // char *p = NULL;
    // p = get_str();
    // printf("p = %s
", p);
    return 0;
}

char *p 是一个 char 类型的指针变量,看代码的意思是希望通过 get_str() 方法给指针 p 赋值。如果你运行一下这段代码会发现它是会报错的,至于为什么会报错就要分析一下内存四区了。

内存四区图

区域 作用
栈区(stack) 由编译器自动分配释放,存放函数的参数值,局部变量值等。
堆区(heap) 一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收。
全局区(静态区) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未被初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。
程序代码区 存放函数体的二进制代码。

现在通过内存四区的理论来分析上面这一段代码。

内存四区分析

  1. 声明了一个名为 buf 的变量
  2. 调用 get_str()
    1. 声明一个数组 str ,它是一个局部变量所以再栈区
    2. 字符串 “abcdef…” 是字符串常量,所以再全局区(静态区)
    3. str = “abcdef” 会将全局区的字符串常量拷贝到 str 数组内存中
  3. 紧接着 get_str() 调用结束,与之相关的内存就会被销毁
  4. strcpy() 希望将 get_str() 返回的字符串拷贝给 buf,但是因为这一块内存已经被销毁了,就会报错

注:有时候以上的操作可能会成功,这可能是因为没有及时销毁,可以用我上面注释掉的指针操作的代码做练习,用内存四区的方式分析,赋值是一定不会成功的,原理相同。

正确的赋值方式

我们可以使用 malloc() 将变量保存在堆区,堆区的内存在函数执行完之后不会立即销毁,而是由我们自己销毁,或者再程序运行完之后由操作系统来销毁。

内存四区分析2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char *get_str2()
{
    char *tmp = (char *)malloc(100);
    if(tmp == NULL)
    {
    return NULL;
    }
    strcpy(tmp, "adsaffa");
    return tmp;
}

int main()
{
    char buf[128] = {0};
    // strcpy(buf, get_str());
    // printf("buf = %s
", buf);
    char *p = NULL;
    p = get_str2();
    if(p != NULL)
    {
        printf("p = %s
", p);
        free(p);
        p = NULL;
    }
    return 0;
}

使用指针的几个注意事项

这一小节属于使用指针过程中遇到的一些问题,以及如何避免,有个故事说:人如果知道自己会死在哪里,就死也不要去那里就行了。使用指针也一样,指针使用会遇到很多的坑,知道哪里有坑就要尽可能避免。

修改内存时要保证内存可写

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main()
{
    char *p = "abcdef";
    char q[] = "fdasfafdfas";
    p[2] = "1”; // 会报错 err
    q[2] = "1";
    printf("p: %s
", p);
    printf("q: %s
", q);
    return 0;
}

修改 p[2] 会报错,因为 abcdef 是字符串常量,保存在全局区(静态区),所以它是不能被修改的。使用指针的第一个事项就是修改内存的时候保证内存可写。

指针的步长

指针的步长由指针指向的内存类型决定,比如说 int p 步长就是 4,chat p 步长就是 1。步长的意思就是每次 p++ 操作之后,地址移动的距离。可以分别打印 p 和 p++ 的值看一看就知道了。

由此还想讲一个问题就是如何计算一个动态数组的大小。比如 int a[] = {1, 2, 3, 4},可以使用 sizeof(a)/sizeof(a[0])的方式计算数组大小。

不允许 NULL 或者未知非法地址拷贝内存

1
2
char *p =NULL;
strcpy(p, “1232143”); // 报错

strcpy() 执行的操作时将字符串拷贝给指针 p 所指向的内存,此时指针 p 指向的内存是 NULL,就是说没有执行任何内存,字符串也就没地方拷贝,自然会发生错误。

如果我们让char* p = 0x11; 任意赋一个值,也会报错,向未知非法地址拷贝也是不被允许的。 正确的做法就是将 p 指向一个已经分配好内存的指针,比如 char *q = “fdsafas” 。

所以当我们给一个使用 malloc() 分配内存之后应该习惯性地加一个判断,比如int *p = (int *)malloc(sizeof(int));之后应该进行判断if(p == NULL)如果为空则说明内存分配失败,就应该对异常进行处理。一直对应的,释放内存是有个也应该讲指针赋值为空free(p); p = NULL;

通过指针间接赋值

我们定义一个函数会有返回值,返回值可能是一个结果、可能表示一种状态。比如说int findMax(int *a, int n);在这个函数中我们出入一个数组,然后返回这个数组中最大的值。

那现在如果我想返回这个数组的最大值、最小值该怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int fun(int *a, int n, int *max, int *min)
{
    if(a == NULL || n <= 0)
    {
        return -1;
    }

    int tmp_max, tmp_min, sum = *a;
    for(int i = 1; i < n; i++)
    {
        if(a[i] < tmp_min)
        {
            tmp_min = a[i];
        }

        if(a[i] > tmp_max)
        {
            tmp_max = a[i];
        }
    }

    max = &tmp_max;
    min = &tmp_min;

    return 0;
}

int main()
{
    int a[5] = {2, 6, 9, 10, 1};
    int max = 0;
    int min = 0;

    int res = fun(a, 5, &max, &min);

    if(res == -1)
    {
        perror("fun() err.");
        return -1;
    }

    printf("max = %d, min = %d
", max, min);

    return 0;
}

定义一个函数int fun(int *a, int n, int *max, int *min);

  1. 返回值表示成功或者失败
  2. a 是数组指针
  3. n 是数组大小
  4. max 和 int 是 int 类型的指针

关键就在于 max 和 int 这两个指针,它们指向的两个内存分别保存数组的最大和最小值,而这两个指针是由主函数传入的,此时就完成你了最大值和最小值传递。

通过指针间接赋值是指针很强大的一个功能,我们可以看到很多函数都是这样定义的。

原文地址:https://www.cnblogs.com/shuiyj/p/13185160.html