指针

指针

指针存储的是变量的内存地址

1
2
3
4
5
6
7
void main(){
int i = 60;
// 创建一个int类型的指针,并将i的地址赋给它
int* p = &i;
printf("%#x\n", p);
system("pause");
}

在指针中,* 称为取内容运算符,指针变量指向的是变量的地址,而拥有了变量的地址,我们可以直接通过指针操作其指向的地址中的内容;

1
2
3
4
5
6
7
8
9
10
11
12
void main(){
int i = 60;
// 创建一个int类型的指针,并将i的地址赋给它
int* p = &i;

printf("修改前:%d\n", i);
// 通过指针得到地址上指向的内容,直接进行修改
*p = 2;
printf("修改后:%d\n", i);

system("pause");
}

为什么需要区分指针类型

指针在中保存的都是地址,而无论是基本类型还是类对象,其地址格式都是一样的16进制数,按理
来说,只需要一种类型的指针就足够了。但是,实际上,指正还有另一个功能,就是取地址内容!当
用int类型的指针去指向double类型的地址,虽然没有报错,但是在取内容时,取到的数据格式是
double数据的整数部分,也就是,指针类型在取值时起到确定数据类型的作用

指针的地址指明开始读取数据的内存地址,指针的类型指明读取的数据长度,即读取结束的位置;

空指针

如果指针创建时,如果没有指向,我们需要默认为其赋予空指针NULL;

1
int *p = NULL;

空指针指向的地址为0X000000,并且其内容不允许被访问;

操作系统只允许应用程序访问其申请的内存区,其他的内存访问都是禁止的!

多级指针

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
void main(){
int i = 40;
int* p1 = &i;
int** p2 = &p1;
printf("i的值:%d\n", i);
printf("p1保存的是i的地址:%#x\n", p1);
printf("p2保存的是p1的地址:%#x\n", p2);

printf("通过p2取出p1保存的i的地址:%#x\n", *p2);
printf("通过p2取出p1保存的i的地址再取出该地址指向的i的值:%d\n", **p2);

**p2 = 2;
*p2 = NULL;

printf("**p2修改的是i的值:%d\n", i);
printf("*p2修改的是p1的值(p1的原值是i的地址):%#x\n", p1);

getchar();
}

i的值:40
p1保存的是i的地址:0xaff7c8
p2保存的是p1的地址:0xaff7c4
通过p2取出p1保存的i的地址:0xaff7c8
通过p2取出p1保存的i的地址再取出该地址指向的i的值:40
**p2修改的是i的值:2
*p2修改的是p1的值(p1的原值是i的地址):0

指针运算

数组使用一块连续的内容地址来保存数据,根据这个特点,只要知道数据的首地址,我们就可以用指针来直接访问数组的所有元素。

在数组中,数组名称保存的就是数组的首地址,所以数组中:
array,&array和&array[0]是等价的!

数组的名称就是一个指针,所以对指针的运算同样适合于数组名。

指针的加法和减法运算能够让指针向前或向后移动对应数组类型的字节数,即重新指向下n个或前n个元素;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 数组定义时,不同于Java,[]必须放在后面
int array[] = { 1, 2, 3, 4, 5 };

// 这三张方式是等价的
printf("%#x\n", array);
printf("%#x\n", &array);
printf("%#x\n", &array[0]);

// 指针运算
int* p = array;
// 让指针向后移动sizeof(int)个字节
p++;
printf("%d\n", *p); // 2

// 移动两个sizeof(int)个字节
p += 2;
printf("%d\n", *p); // 4

// 向前移动一个sizeof(int)个字节
p--;
printf("%d\n", *p); // 3

getchar();

指针运算循环为数组赋值:

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
void main(){
int uids[5];
int* p = uids;
// 通过指针对数组进行赋值(方式1)
int i = 0;
for (; i < 5; i++){
uids[i] = i;
}

int j = 0;
for (; j < 5; j++){
printf("pre:%d\n", uids[j]);
}

// 方式2
int* p2 = uids;
for (; p2 < uids + 5; p2++){
*p2 = i;
i++;
}

j = 0;
for (; j < 5; j++){
printf("post:%d\n", uids[j]);
}
getchar();
}

函数指针

函数指针即指向函数的指针,在其他高级语言中,使用了函数类型,匿名函数来实现函数指针的效果,比如Kotlin就是这样实现的。

声明函数指针变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int msg(char* msg,char* content){
MessageBox(0, msg, content, 0);
return 0;
}

void main(){
// C程序中函数,在内存中保存的是一系列汇编指令的集合
// 函数名称保存了函数的首地址
printf("%#x\n", msg);

// 定义函数指针
// 返回值(指针名)(参数列表) = 函数名
int(*fun_p)(char* msg, char* content) = msg;
fun_p("aaa", "bbb");

getchar();
}

将函数指针作为参数传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数指针作为参数
void msg(int(*funp)(int a, int b), int m, int n){
int result = funp(m, n);
printf("%d\n", result);
}

int add(int a, int b){
return a + b;
}

void main(){
// 将函数名作为参数传递给函数指针
msg(add, 1, 2);
getchar();
}

指针与数组

1
2
// 定义数组
int a[] = { 78, 34, 73, 25, 80, 90 };
  • a+i 等价于 &a[i],*(a+i)等价于a[i],后者只是简写方式,在最古老的C语言里是没有中括号的;
1
int* p = a;
  • p[i], &p[i],*(p + i), p + i都可以访问数组,也就是说p等价于a;
1
2
3
// 定义二维数组
// 定义二维数组有两种方式
int a[2][3] = {95,82,56,17,29,30};
  • &a 和 a 地址都是相同的,但是意义不同,比如&a+1,并不指向数组中的任一个元素,
    而是指向数组最后一个元素地址的下一个地址(即跨越了一个数组容量),而a+1表示指向第二行的指针;
  • a表示指向第一行第一个元素,a+1则是指向第一行第二个元素;
  • (a+1)指向了第二行第一个元素,(a+1)+1指向了第二行第二个元素;

数组和指针存储字符串的区别

C中可以使用两种方式来存储字符串,分别是字符数组和字符指针,两者的区别在于:

1、字符数组存储的字符串是可以进行修改的(数组本身就是可变的),而字符指针存储的数组不可以修改(相当于常量);

2、在结构体中使用时,字符数组必须声明其容量,而字符指针容量可以是任意的;

3、在访问字符数组和字符数组时的区别:

1
2
char* ap = "hello";
char a[] = "hello";
  • a和&a都是表示数组的首地址,不能通过*a访问数组中的内容;

  • ap表示的是数组的首地址,等价于a、&a和&a[0];

  • *ap 表示 a[0];

  • &ap 表示的是指针变量的地址,跟字符数组是无关的;

字符数组不支持整个重新赋值,但支持对单个元素进行修改;
字符指针支持对整个进行重新赋值,但不支持对单个元素进行修改;

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
void main(){
// 字符数组存储字符串
// 下面三种方式等价
// 字符数组表示的字符串是可修改的
char str1[] = { 'c', 'h', 'i', 'n', 'a','\0'};
char str2[6] = { 'c', 'h', 'i', 'n', 'a' };
char str3[] = "china";

// 通过字符指针实现
// 字符指针表示的字符串是不可修改的常量
char *str = "china";

str3[0] = 'v';
printf("%s\n", str1);
printf("%s\n", str3);

// 通过指针运算截取字符串
str += 3;
while (*str){
printf("%c", *str);
str++;
}

getchar();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main(){
char arr[10] = "haha";

// 对数组整个赋值是禁止的
// arr = "lili";
// 只能通过strcopy函数实现
strcpy_s(arr, 10,"hihi");

// 但是支持对其中某个元素进行直接修改
arr[0] = 'a';

char* ap = "nihao";

// 不支持直接修改某个元素
//ap[0] = 'w';
// 支持对整个进行修改
ap = "dajiahao";

printf_s("%s\n", arr);
printf_s("%s\n", ap);

getchar();
}

4、当在结构体中声明字符数组和字符指针时,其赋值方式也是不同的;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct  man{
int age;
char name[10];
char* sex;
};

// 初始化结构体
// 方式1
struct man m1 = { 10, "jj" };

// 方式2
struct man m2;
m2.age = 10;
// 对于数组类型,只能使用strcopy函数进行赋值
//strcpy(m2.name, "lala");
sprintf(m2.name, "uu");

// char* 类型可以直接赋值
m2.sex = "girl";

// 只能在声明时进行赋值,否则只能用"."访问符进行赋值
//m2 = { 10, "jj" };