指针是 C 语言的灵魂,现在对于指针的掌握肯定是不透彻的,然学习是一个迭代的过程,姑且写出目前自己的理解。
指针的概念1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include
#include
#include
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;
}
}
先来看这样一段代码
p 是一个指针变量,*p 是指针指向的内存空间的保存的值。
&a 表示取变量 a 所在内存的地址,p = &a 表示将内存 a 所在的地址值赋值给 p,相当于指针 p 指向 a 所在的内存。
接下来分别打印 p 和 *p 来验证上面的说法
最后我们释放内存空间
明确几个概念:
指针是一个变量
注意区分指针变量和它指向的内存块改变指针变量的值不会影响指针指向的内存区域
改变指针指向的内存区域也不会改变指针变量的值
内存四区
先来看这样一段代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include
#include
#include
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)
一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收。
全局区(静态区)
全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未被初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。
程序代码区
存放函数体的二进制代码。
现在通过内存四区的理论来分析上面这一段代码。
声明了一个名为 buf 的变量
调用 get_str()声明一个数组 str ,它是一个局部变量所以再栈区
字符串 “abcdef…” 是字符串常量,所以再全局区(静态区)
str = “abcdef” 会将全局区的字符串常量拷贝到 str 数组内存中
紧接着 get_str() 调用结束,与之相关的内存就会被销毁
strcpy() 希望将 get_str() 返回的字符串拷贝给 buf,但是因为这一块内存已经被销毁了,就会报错
注:有时候以上的操作可能会成功,这可能是因为没有及时销毁,可以用我上面注释掉的指针操作的代码做练习,用内存四区的方式分析,赋值是一定不会成功的,原理相同。
正确的赋值方式
我们可以使用 malloc() 将变量保存在堆区,堆区的内存在函数执行完之后不会立即销毁,而是由我们自己销毁,或者再程序运行完之后由操作系统来销毁。
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
#include
#include
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
#include
#include
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,chatp 步长就是 1。步长的意思就是每次 p++ 操作之后,地址移动的距离。可以分别打印 p 和 p++ 的值看一看就知道了。
由此还想讲一个问题就是如何计算一个动态数组的大小。比如 int a[] = {1, 2, 3, 4},可以使用 sizeof(a)/sizeof(a[0])的方式计算数组大小。
不允许 NULL 或者未知非法地址拷贝内存1
2char *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
#include
#include
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);
返回值表示成功或者失败
a 是数组指针
n 是数组大小
max 和 int 是 int 类型的指针
关键就在于 max 和 int 这两个指针,它们指向的两个内存分别保存数组的最大和最小值,而这两个指针是由主函数传入的,此时就完成你了最大值和最小值传递。
通过指针间接赋值是指针很强大的一个功能,我们可以看到很多函数都是这样定义的。