【C】 重点:指针
指针,一个令初学 c 语言者特别头疼又避不开的东西,我们想要知道我们的程序到底在内存中做了什么事情,那起码得接触指针,要了解指针在虚拟内存中起着一个什么样的作用,能够干什么,这么干有什么好;学好指针可以更好的帮助我们理解程序,为进后的编程道路打下基础。
# 1. 基本入门
基本概要
日常中类似于使用到指针的地方有很多,我们的指针也是根据日常生活中来的,就比如储药柜,我们会给每一号药材(药品)贴上一个记号,这个记号可以是药名,可以是编号,只要方便于我们找到我们想要拿的药,c 程序也一样。
我们可以把一块内存空间 想象成一个 储药柜,每个储药柜贴上了一个标签,这个标签对应的就是我们的 内存地址,而这个地址就可以称作为指针
,我们通过指针就可以访问该地址里面存放的值,进行各种操作。
指针入门程序
- 指针:对应内存的地址
- 指针变量:保存内存的地址的变量
- &: 通过该运算符拿到变量的地址,作用于变量
- *:解引用运算符,通过该运算符拿到该地址所对应的变量,作用于指针
#include <stdio.h>
int main() {
int a = 5;
// int*: 代表定义一个int型的指针变量
// 定义了一个 指针变量p, 来保存a变量的内存地址(也可以说让 p 指向 a)
int* p = &a;
// 读取p的值,即内存地址: p为 0x25C8E2
printf("pointer p=%p\n",p);
printf("a=%d\n",a);
// 读取 p 地址里的变量,我们会发现就是刚开始定义的5;
printf("*p=%d\n",*p);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. 指针的基本使用
使用指针做交换变量
#include <stdio.h>
#include <stdlib.h>
void testSwap(int* p, int* q);
int main() {
int a = 5;
int b = 6;
testSwap(&a,&b);
// 输出结果 a=6,b=5
printf("a=%d,b=%d",a,b);
}
// 封装一个函数,专门用来做交换变量
void testSwap(int* p, int* q){
int temp = *p;
*p = *q;
*q = temp;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
解决函数无法返回多个值
我们知道,函数是无法返回多个值的,但是参数却可以传多个,所以我们可以传多个指针变量来修改我们想要修改的多个值,如下案例:
/**
* 我们要在一个数组中获得最小值和最大值,并赋值到我们 main() 函数中的变量上
* 很显然,这是返回值所做不到的; 但是我们利用指针就可以
* 1. 传入存放最小值和最大值的指针变量
* 2. 通过遍历判断, 用 *运算符访问到该地址所存放的变量将其修改
* */
#include <stdio.h>
#include <stdlib.h>
// 函数原型,类似于规范
void getLimitV(int a[], int length, int* min, int* max);
int main() {
int a[] = {1,4,3,99,23,89,5,8,9};
int min,max;
// printf("min=%d,max=%d",min,max); // 全局变量默认值为0,局部变量没有默认值,这个是随机给的
// sizeof 求所占内存的字节数量
getLimitV(a,sizeof(a)/sizeof(a[0]),&min,&max);
//最后我们会发现: min=1,max=99
printf("min=%d,max=%d\n",min,max);
}
// 传入数组,数组长度,存放最大值和最小值的指针变量
void getLimitV(int a[], int length, int* min, int* max) {
if (length <=0) {
return;
}
*min = a[0];
*max = a[0];
for (int i = 1; i < length; i++) {
if (a[i]>*max) {
*max = a[i];
}
if (a[i]<*min) {
*min = a[i];
}
}
}
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
# 3. 指针的读写问题
在我们做程序设计的时候, 安全应该要放在一地位的, 所以我们想让某些 变量只能看, 不能改, 我们的 c 是没有 修饰符来定义变量的访问权限一说的, 这个时候 我们的指针又可以派上用场了
// 主要抓住以下区别: 就看 const 在 * 前还是后
const int* p; // 指针变量所指向内存地址里的 值 无法进行修改
int* const p; // 指针变量所指向的内存地址无法进行修改, 也就是说无法再指向别处啦
2
3
只能读值, 无法改值:
void testReadWrite() {
int a = 5;
int b = 6;
const int* p = &a; // 表示不可以用 p指针不可修改值 如:*p = 7;
*p = b; // error: assignment of read-only location '*p'
}
2
3
4
5
6
7
只能读地址,无法改地址
void testReadWrite() {
int a = 5;
int b = 6;
int* const p; // 表示指针本身不可以再指向别人 如 p = &a
p = &b; // error: assignment of read-only variable 'p'
}
2
3
4
5
6
7
# 4. 指针与数组
数组 属于 指针 ?
我们知道, 指针变量所对应的值是一个形如 0x24512E 的内存地址, 我们来看看数组变量会打印成什么:
void viewArrayAndPointer() {
int arr[] = {1,2,3,4,5,6,7,8,9};
printf("arr=%p\n",arr); // 直接打印数组: arr=0061FEFC
printf("&arr=%p\n",&arr);// 打印数组所对应的内存地址: &arr=0061FEFC
printf("&arr[0]=%p",&arr[0]);// 打印第一个元素的内存地址: &arr[0]=0061FEFC
}
2
3
4
5
6
通过上面的小案例, 我们惊奇的发现: 数组直接打印为指针变量对应的内存地址了, 那我们可不可以理解为 数组 是一种 特殊的指针变量,或者说数组就是指针变量呢? 我们来大胆猜想一下:
/*
由于数组在内存中会开辟一块连续的内存空间,
我们使用索引就可以访问到 具体所对应的值,
那我们利用指针变量 用 *运算符能不能访 问数组变量呢,
我们来做如下遍历代码:
*/
// example 1: 下标访问
void traverseArrayByIndex(const int arr[],int len) {
for (int i = 0; i < len; i++)
{
if (i == len - 1) {
printf("%d",arr[i]);
} else {
printf("%d, ",arr[i]);
}
}
printf("\n");
}
// example 1: 指针访问
void traverseArrayByPointer(const int* arr,int len) {
while (len > 0)
{
if (len == 1) {
printf("%d",*arr);
} else {
printf("%d, ",*arr++);
}
len--;
}
printf("\n");
}
// 当两个函数一起调用的时候,发现遍历出来的结果是一模一样的
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
我们就可以得出如下的结论:
- 数组其实其实就是开辟了一段连续的空间来存放我们的元素
- 数组就是指针,但是指针不一定是数组,因为数组必须要有连续空间
- 建议使用指针,因为一般直接操作地址会比较快
# 5. 动态分配内存空间
malloc / free
- malloc:相当于在内存中借用了一部分空间,这一部分空间不会自动释放
- free: 在程序适当的时候使用这个函数即释放空间
- realloc:改变空间大小
- calloc:分配一块二维存储空间
如果没有在恰当的时候释放掉创建的空间,当程序时间运行很长之后会出现严重内存泄漏,造成程序的崩溃,闭包就是典型
# 6. 字符串相关
字符串的创建方式
字符串的实质就是字符数组,关于字符串有三种写法:
// 传统数组写法,不推荐
char x[] = {'h','e','l','l','o','\0'}; // 注意,一旦是字符串结尾必须要加 \0 以代表字符串的结束,
// 数组类型字面量写法
char x[] = "hello"; // 字面量写法后面程序会自动给我们加上 \0 下同
// 指针字面量写法
char* x = "hello";
2
3
4
5
6
7
8
9
10
需要注意区别的是:
- 数组类型的字符串变量是存在
栈
中, 指针类型的字符串是存在堆
中,只是将堆的地址存放在栈中的变量里 - 字符串数组的指针是 const 类型 不可被修改,字符串指针里面的 字符串值在文字常量区 其值不可被修改
字符串数组的创建方式
char* test[] = {"test1" "test2" "test3"}; //指针创建,这种形式数组中的字符串为常量,无法修改
char test[][10] = {"test1" "test2" "test3"}; //二维数组,这种方式需要定义大小
2
3