xiaohuihui
for me

C指针

2020-04-25 22:25:50
Word count: 3.1k | Reading time: 12min

C指针学习

先普及一下C语言中每个数据类型所占的字节。

1
2
3
4
5
6
7
8
9
10
11
byte1个字节

char1个字节

short2个字节

int4个字节

long4个字节

double: 8个字节

一个字节(byte)=八位(bit)

什么是内存?内存,CPU,硬盘之间的关系?

一个C语言程序必须载入内存才能运行(任何程序都是这样),CPU也被设计为只能从内存中读取数据和指令.

在我理解的程序运行机制中,内存是CPU和硬盘的一个中间件,没有内存,CPU是无法执行的,CPU从内存或缓存中取出指令,放入指令寄存器,并对指令译码进行分解,进而对数据进行处理。这么说吧,计算机中所有的程序运行都是在内存中进行的,因此内存对计算机性能的影响非常大。数据由传输速度较慢的硬盘通过内存传送到CPU进行处理。不过内存是带电存储的(断电数据就会消失),而且容量十分有限,所以要长时间储存程序或数据就需要使用硬盘。

什么是指针?

  • 指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

为什么使用指针?

  • 任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。一般指针变量指向的是内存中的首地址,指针是根据内存地址来取值的,不是根据内存来取值。

  • 定义了一个int变量num,如下是它在内存中所占的空间及地址。

  • 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
    #include <stdio.h>

    void test()
    {
    int num = 0X01020304; //定义十六进制的数

    int* p1 = &num; //定义指向类型为int的指针内存地址
    printf("*p1=%#x\n", *p1); //0x01020304

    short* p2 = &num; //指向类型short占2个字节,所以是int类型内存地址的一半
    printf("*p2=%#x\n", *p2); //0X0304 在windows和linux中地址是反过来排序的

    char* p3 = &num; //指向类型char占1个字节,所以是int内存地址的四分之一
    printf("*p3=%#x\n", *p3); //0x04

    short* p4 = &num;
    p4++; //p4+1后 地址跨度为2格 因为指向类型short占俩个字节,而int内存地址占四格
    printf("*p4=%#x\n", *p4); //0X0102


    /**因为一般指针取值都是根据指向类型而从左往右取值的,我们知道int占四个字节,所以可以理解为四个方块
    ,而假如我们要取中间俩个地址的值,就要用到类型转化,首先要跨一个字节(+1)(要看指向类型而定义,这里指char),即跨一个方块,然后在进行转换为2个字节(short),即可获得俩个中间俩个字节所占的内存地址**/

    char* p5 = &num;
    p5 = p5 + 1;
    printf("*p5=%#x\n", *(short *)p5); //0203
    }


    void test2() //用来测试俩个指针共享数据
    {
    /**指针赋值和int变量赋值一样,就是将地址的值拷贝给另外一个。指针之间的赋值是一种浅拷贝,是在多个编程单元之间共享内存数据的高效的方法。**/
    int num = 10;
    int* p1 = &num;
    int* p3 = p1;
    printf("*p1的值为:%d\n", *p1); //10
    printf("*p3的值为:%d", *p3); //10
    }


    int main()
    {
    test();
    return 0;
    }

    输出结果图片:

    image-20200426002039662

一维数组与指针

  • 假定我们声明数组a[4]和数组b[4],分别利用指针p和q来指向俩个数组。一般指针*p指向的即使a[0]的内存地址,而指针q指向的也是数组b[0]的内存地址,当指针每次加1的时候,根据指向类型,可判断跨度,在这里我声明俩个指针的指向类型为int类型,每次加一,跨自身为1,所以在这里,指针每次加一,数组元素也加一。

image-20200426111824054

举例:

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
#include <stdio.h>

void main()
{
int* p, * q, a[5], b[5], i;
p = &a[0]; //指针p指向数组a的内存地址 a[0]表示数组的第一个元素的地址
q = b; //b[0]或者b用来表示数组第一个元素的地址
printf("请输入数组a的值:\n");
for (i = 0; i < 5; i++) {
scanf_s("%d", &a[i]);
}
printf("请输入数组b的值\n");
for (i = 0; i < 5; i++) {
scanf_s("%d", &b[i]);
}
printf("数组a是:\n");
for (i = 0; i < 5; i++) {
printf("%5d", *(p + i)); //使用指针输出一维数组 每次循环+1
}
printf("\n");
printf("数组b是:\n");
for (i = 0; i < 5; i++) {
printf("%5d", *(q + i));
}
printf("\n");

}

赋值与输出结果为:

image-20200426112530372

二维数组与指针

二维数组在概念上是二维的,有行也有列。

一般排序应该类似于一个矩阵,但是在C语言的内存中,它是一行排序过来的,意思就是占用一连续的内存。

1
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

1-160H11I6303X

就像上图一样排序。

我们可以把它分解为3个一维数组a[0],a[1],a[2],而在每个一维数组又包含了4个元素,比如a[0]数组包含了a[0],a[1],a[2],a[3]。如此一来,是不是很清晰透彻,在一维数组中,指针每次+1,一维数组的元素也就会自增+1,所以,在二维数组中,指针+1也跟一维数组中的指针+1相同。二维每次+1,它就垮了一个数组元素,而数组元素中的自然也要加进去,相当于它垮了四个数组元素的字节长度。下面举例说明:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int(*p)[4] = a;
printf("%d\n", sizeof(*(p + 1)));

return 0;
}
/*指针p指向的是数组a的开头a[0],就是指向第0行,当指针+1的时候,它就会跨一行,而一行里面有四个int类型的元素且p指向的是int类型,每个元素占4个字节,所以当跨一行的时候,就会前进16个字节*/

image-20200426135047894

*(p+1)+1则肯定是第一行的第一个元素,在此不要混淆。

指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个变量为指向指针的指针变量或指向指针的指针。

声明格式:类型标识符 **指针变量名 ,也可以这样声明:

1
int **p   ==  int *(*p)

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main()
{
int i;
char** p; //定义指针类型为char的指针
char* arr[] = { "A","B","C","D","E","F","G","H" };

for (i = 0; i < 7; i++) {
//为什么使用指向指针的指针,因为arr也是一个指针,如果要指向arr这个指针,需要定义一个指向指针的指针,否则会报错。
p = arr + i; //指针p每次+1
printf("%s\n", *p);
}
return 0;
}

image-20200426142414946

相当于一个指针指向的地址,赋给里一个指针。所附的指针,指向一个已经指向地址的指针的地址。

指针变量作函数参数

函数的参数,可以传很多东西,在php中函数的变量可以传对象,也就是闭包,还有数组,字符串各种,都可以传,但是php的话,声明变量不像C语言和java需要声明数据类型,因为在php中,这些东西都是在底层封装好的。

但是在C语言中的话,像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合。

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

void swap(int a,int b)
{
int temp; //临时变量
temp = a;
a = b;
b = temp;
}

int main()
{
int a = 10, b = 20;
swap(a, b); //调用自定义的交换函数
printf("a=%d,b=%d\n", a, b);
return 0;
}

/*按照常理,此处的输出结果应该是a=20,b=10然而结果并不是,它的结果是确是a=10,b=20根本没有交换,这是为什么,因为swap函数内部的a,b和main函数内部的a,b是不同的变量,占用着不同的内存*/

image-20200426165041410

当我们改用指针作变量的时候,效果就会不一样.

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

void swap(int *p,int *q)
{
int temp; //临时变量
temp = *p;
*p = *q;
*q = temp;
}

int main()
{
int a = 10, b = 20;
swap(&a, &b); //调用自定义的交换函数
printf("a=%d,b=%d\n", a, b);
return 0;
}

image-20200426165506751

调用 swap() 函数时,将变量 a、b 的地址分别赋值给 p1、p2,这样 p1、p2 代表的就是变量 a、b 本身,交换 p1、p2 的值也就是交换 a、b 的值。

函数指针

C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。

1
2
//声明格式:
类型标识符 (指针函数变量名)(参数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

int add(int x, int y) {
return x + y;
}

int sub(int x, int y) {
return x - y;
}

int (*f)(int x, int y); //定义指针函数

int main()
{
f = &add;
printf("10+20=%d\n", (*f)(10, 20)); //30

f = &sub;
printf("10-20=%d\n", (*f)(10, 20)); //20

return 1;
}

结果如下:

1

每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。

结构体变量指向指针

什么是结构体? 结构体是由基本数据类型构成的、并用一个标识符来命名的各种变量的组合,结构体中可以使用不同的数据类型。数组是一组由相同类型的数据组成的集合,而当我们在编程中,比如在学生信息表中,会有不同类型的变量,比如姓名是字符串,年龄是整数,成绩中有浮点小数,这个时候,就不能用数组来存放,就要用到结构体(Struct)来存放一组不同类型得数据。

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

//使用struct关键字定义结构体变量
struct Book
{
char iName[20];
int iNumber;
char a[20];
}book = { "计算机科学",56,"134-658" };

int main()
{
struct Book * p; //定义指向结构体变量的指针p
p = &book; //该指针指向结构体book的首地址
printf("书的名字是%s\n", (*p).iName); //指针在调用的时候,需要在外层加括号(*p),
printf("书架的编号是:%d\n", (*p).iNumber);
printf("图书的编号是:%s\n", (*p).a);
return 0;
}

指针的好处:

使用指针可以带来如下的好处:

(1)可以提高程序的编译效率和执行速度,使程序更加简洁。

(2)通过指针被调用函数可以向调用函数处返回除正常的返回值之外的其他数据,从而实现两者间的双向通信。

(3)利用指针可以实现动态内存分配。

(4)指针还用于表示和实现各种复杂的数据结构,从而为编写出更加高质量的程序奠定基础。

(5)利用指针可以直接操纵内存地址,从而可以完成和汇编语言类似的工作。

(6)更容易实现函数的编写和调用。

以上都是讲了一些指针的基础,没有更加强势的体现出指针的优势所在,因为正在进行数据结构学习中,后期将结合数据结构,实现指针的优势所在。

Author: 小灰灰

Link: http://xhh460.github.io/2020/04/25/C%E6%8C%87%E9%92%88/

Copyright: All articles in this blog are licensed.

< PreviousPost
动态链表
NextPost >
ajax不执行回调函数的原因
CATALOG
  1. 1. C指针学习
    1. 1.0.0.1. 先普及一下C语言中每个数据类型所占的字节。
    2. 1.0.0.2. 什么是内存?内存,CPU,硬盘之间的关系?
    3. 1.0.0.3. 什么是指针?
    4. 1.0.0.4. 为什么使用指针?
    5. 1.0.0.5. 一维数组与指针
    6. 1.0.0.6. 二维数组与指针
    7. 1.0.0.7. 指向指针的指针
    8. 1.0.0.8. 指针变量作函数参数
    9. 1.0.0.9. 函数指针
    10. 1.0.0.10. 结构体变量指向指针
    11. 1.0.0.11. 指针的好处: