当前位置:   article > 正文

C语言 第十章 指针_#include int main(){ float x,y,opr(float

#include int main(){ float x,y,opr(float,char,float); char op;


“指针是C语言的灵魂”,指针非常重要。这篇笔记是我在小甲鱼课笔记的基础上,结合学校里的课以及习题做出来的,希望能有所帮助。

1 指针

1.1变量的指针和指针变量

  • 变量的指针就是变量的地址
  • 指针变量是一个变量,用于存放地址。

1.2 定义指针变量

  • 类型名 *指针变量名
char *pa; // 定义一个指向字符型的指针变量
int *pb; // 定义一个指向整型的指针变量
  • 1
  • 2

1.3 取地址运算符和取值运算符

  • 存储单元的编号就称为内存的地址(指针),是一个十六进制的数字。
  • 如果需要获取某个变量的地址(变量的指针),可以使用取地址运算符(&),也称为间接运算符:
char *pa = &a;
int *pb = &f;
  • 1
  • 2
  • 如果需要访问指针变量指向的数据,可以使用取值运算符(*):printf("%c, %d\n", *pa, *pb);
  • 注意:’*'在指针变量的定义和取值中都被用到,但意义不同,定义的时候代表定义指针变量,其他代表取值运算符
  • 示例
#include <stdio.h>

int main()
{
    char a = 'F';
    int f = 123;

    char *pa = &a;
    int *pb = &f;

    //间接访问,取值运算符➕指针变量
    printf("a = %c\n", *pa); 
    printf("f = %d\n", *pb);

    *pa = 'C'; //修改等于修改变量
    *pb += 1;

    printf("now a = %c\n", *pa);
    printf("now f = %d\n", *pb);

    //检测指针变量的内存大小,64位编译器中占8个字节
    printf("sizeof pa = %lu\n", sizeof(pa));
    printf("sizeof pb = %lu\n", sizeof(pa));
    
    //打印指针变量的内存地址
    printf("the addr of a is: %p\n", pa);
    printf("the addr of b is: %p\n", pb);

    return 0;
}
  • 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
  • 运行结果
a = F
f = 123
now a = C
now f = 124
sizeof pa = 8
sizeof pb = 8
the addr of a is: 0x7ffee83d9667
the addr of a is: 0x7ffee83d9660
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

1.4 避免访问未初始化的指针(野指针)

扩展内容,一般人注意即可

int *a;
*a = 123;
  • 1
  • 2
  • 野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。
  • 野指针与空指针(NULL)不同,空指针在C语言中定义为define NULL ((void *)0)
  • 可见空指针指向0地址,而野指针指向不确定的地方。野指针产生主要有两个原因:
  • 1.指针变量未初始化:任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。
  • 2.指针释放之后未置空:有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针删除。为什么野指针会产生错误呢?现在有指针int* a和一个变量int b;如果a没有赋值的话,他可能指向任意地方,假设它指向了b的地址,现在又使用了a的指向,如*a=c,那么就在无意间把b的值给改变了。
  • 产生野指针以上面第一个原因居多,可以按照以下方法避免:
  • 1.定义指针后初始化为NULL;
  • 2.使用前制定一个可用地址;
  • 3.引用之前判断是否为NULL;
  • 4.使用完后赋值为NULL。

1.5 指针作为函数的参数

  • 指针作为函数参数,接收变量地址
    指针作为函数的参数时,传递给函数的是某一个变量的地址,这种情况又称为地址传递。
    在这里插入图片描述

在这里插入图片描述

  • 指针作为函数参数,接收一维数组地址
    数组名作参数时,作用与指针相同
    在这里插入图片描述

1.6 返回值为指针类型的函数

函数返回一个指针,该指针指向一个已定义的任一类型的数据。

格式:
类型 *函数名(形参表)
{ 函数体 }

在这里插入图片描述


2 数组和指针

2.1 为什么这么像

  • 指针和数组是好基友,关系密切,但不是同一个东西
  • 一段代码
#include <stdio.h>

int main()
{
    int a;
    int *p = &a;  //初始化一个指针变量,将a的地址赋给p

    printf("请输入一个整数:");
    scanf("%d", &a);  //传递参数到a地址中
    printf("a = %d\n", a); //打印a

    printf("请重新输入一个整数:");
    scanf("%d", p); //传递参数到p,就是a的地址中,两者效果一样。注意不能是&p
    printf("a = %d\n", a);  //打印a
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 运行结果
请输入一个整数:3
a = 3
请重新输入一个整数:5
a = 5
  • 1
  • 2
  • 3
  • 4
  • 再看一段代码
#include <stdio.h>

int main()
{
    char str[128]; // 由针对这里的写法,因为scanf函数的不安全,容易产生内存错误,因此规定char数组长度


    printf("请输入百度的网址:");
    scanf("%s",str); //str前没有&

    printf("百度的网站:%s\n", str);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 运行结果
请输入百度的网址:www.baidu.com
百度的网站:www.baidu.com
  • 1
  • 2
  • 两段代码比较可以发现。第二个代码中的数组名str,像是一个地址信息

2.2 数组名的真实身份

数组名是数组第一个元素的地址!

  • 验证
#include <stdio.h>

int main()
{
    char str[128]; 


    printf("请输入百度的网址:");
    scanf("%s",str);

    printf("str 的地址是: %p\n", str);
    printf("str 的地址是: %p\n", &str[0]); 
    //由于字符数组每一个元素都是字符,所以访问字符地址时要加上&

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 运行结果
请输入百度的网址:www.baidu.com
str 的地址是: 0x7ffee05c15a0
str 的地址是: 0x7ffee05c15a0
  • 1
  • 2
  • 3
  • 结论:数组名是数组第一个元素的地址

2.3 验证不同类型数组顺序存放

#include <stdio.h>

int main()
{
    char a[] = "baidu";
    int b[5] = {1,2,3,4,5};
    float c[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
    double d[5] = {1.1, 2.2, 3.3, 4.4, 5.5};

    printf("a[0] -> %p, a[1] -> %p, a[2] -> %p\n", &a[0], &a[1], &a[2]);
    printf("b[0] -> %p, b[1] -> %p, b[2] -> %p\n", &b[0], &b[1], &b[2]);
    printf("c[0] -> %p, c[1] -> %p, c[2] -> %p\n", &c[0], &c[1], &c[2]);
    printf("d[0] -> %p, d[1] -> %p, d[2] -> %p\n", &d[0], &d[1], &d[2]);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 运行结果(具体数字跟操作系统有关)
a[0] -> 0x7ffee34df588, a[1] -> 0x7ffee34df589, a[2] -> 0x7ffee34df58a
b[0] -> 0x7ffee34df5e0, b[1] -> 0x7ffee34df5e4, b[2] -> 0x7ffee34df5e8
c[0] -> 0x7ffee34df5c0, c[1] -> 0x7ffee34df5c4, c[2] -> 0x7ffee34df5c8
d[0] -> 0x7ffee34df590, d[1] -> 0x7ffee34df598, d[2] -> 0x7ffee34df5a0
  • 1
  • 2
  • 3
  • 4
  • 结论
  • 字符数组a,元素前后差1字节
  • 整型数组b,元素前后差4字节
  • 单精度c ,元素前后差4字节
  • 双精度d ,元素恰后差8字节

2.4 指向数组的指针

  • 如果要用一个指针指向数组,应该怎么做
//假设已定义数组a
char *p;
p = a; // 语句1
p = &a[0]; // 语句2
  • 1
  • 2
  • 3
  • 4
  • 注意有区别
    • 数组的指针是指数组的起始地址,
    • 数组元素的指针是数组元素的地址。

2.5 指针的算术运算

  • 当指针指向数组元素的时候,我们可以对指针进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素。

  • p+n的意义是p+n*sizeof(T)

  • 示例

#include <stdio.h>

int main()
{
    char a[] = "baidu";

    char *p = a;

    //注意是*(p+1),指针+1指向下一个地址,而不是*p+1(地址+1)
    printf("*p = %c, *(p+1) = %c, *(p+2) = %c\n", *p, *(p+1), *(p+2));

    return 0;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 运行结果

*p = b, *(p+1) = a, *(p+2) = i

  • 对比标准的下标法访问数组元素,这种使用间接访问的方法叫做指针法。(即通过指向数组元素的指针找到所需的元素,使用指针占用内存少,运行速度快,目标程序质量高。 )
  • 需要郑重强调的是:p+1并不是简单的将地址+1,而是指向数组的下一个元素
  • 上条规则,不止在间隔为1的字符数组上适用,换在间隔为4的整型等等其他数组上也一样(自行验证)

2.6 直接将指针法作用于数组名

  • 不定义指针变量,直接用数组名(把数组名看作指针)用指针法去访问数组元素
  • 示例代码:
#include <stdio.h>

int main()
{
    int b[5] = {1,2,3,4,5};

    //char *p = b; 不定义指针变量

    printf("*b = %d, *(b+1) = %d, *(b+2) = %d\n", *b, *(b+1), *(b+2));

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 结果

*b = 1, *(b+1) = 2, *(b+2) = 3

2.7 反转

  • 用指针定义字符串,用下标法访问每一个元素
  • 示例
#include <stdio.h>
#include <string.h>

int main()
{
    char *str = "I love FishC.com!"; //给字符指针初始化
    int i, length;

    length = strlen(str); //获取字符串长度

    for(i = 0; i < length; i++)
    {
        printf("%c", str[i]);
    }
    printf("\n"); //换行舒服一点

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 结果

I love FishC.com!

2.8 字符指针

通常使用两种方法对字符串进行操作:1.字符数组2.字符指针

char *s = “Welcome to C”;
等价于:
char *s;
s = “Welcome to C”;

字符数组&字符指针变量

都能实现字符串的存储和运算。
联系与区别:

  1. 存储:

    • 字符数组中每个元素放一个字符
    • 字符指针变量中存放字符串首地址
  2. 赋值方式:

    • 数组名常量(地址常量),不能赋值
    • 字符指针变量可赋值,

    char *a; a=”I love China!”; √
    char str[14]; str[0]=’I’; √
    char str[14]; str=”I love China!”; ×

  3. 初始化的含义

    char *a=”I love China!”; √
    char *a; a=”I love China!”; √

    char str[14]= ”I love China!”; √
    char str[14]; str=”I love China!”; ×

    char *a; scanf(“%s”,a); ×,a未初始化
    char *a,str[10];
    a=str; scanf (“%s”,a); √

  4. 存储单元的内容

    • 编译时为字符数组分配若干存储单元
    • 对字符指针变量,只分配一个存储单元
  5. 指针变量的值是可以改变的;数组名代表数组首地址,是常量,不能改变。

#e.g.改变指针变量的值。
#include <stdio.h>
int main()
{ char *a="I love China!";
   a=a+7; 
   printf(%s\n”,a); 
   return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
> 不能改为  
> char a[]=“I love China!”;a=a+7;
  • 1
  • 2
  1. 字符数组中各元素的值是可以改变的
    • 字符指针变量指向的字符串常量不可以被改变。

    char a[]=”House”,*b=”House”(b指向常量);
    a[2]=’r’; √
    b[2]=’r’; ×

    1. 转自字符指针和字符数组的初始化

char* str1 = “abcd”;
char str2[] = “abcd”;

char* str1 =”abcd”
含义是先新建一个字符串,内容是abcd 然后str1是一个头指针,指向这个串. 但是这个新建串是作为const存在的,并不是一个可以编辑的变量,因此,一旦你想更改其中的值,程序就会出错。根据定义:指向常量字符串的指针不能更改指针指向的内容,但是可以改变本身的值,既是执行*(str1+2)=’w’;(错误) str1=”bcvcbvv”(正确)

相应的
char *str1 = (char *)malloc(5*sizeof(char));
str1 = “abcd”;
相当于开辟一个5个长度的数组,头指针是str1,但是第二句又把str1指向的位置变了,之后还是不能操作str1的内容.

char str2[] = “abcd”
这个相当与指针常量,就是本身是个常量<因为str2就是数组的一个引用,引用本身就是指针常量>这个的含义是在内存中开辟一个数组,然后向该数组中填充”abcd”, 是一个可操作的变量.所以初始化的时候可以这么写,就能在之后更改其中的内容了.
这里 *(str2+2)=’W’(准确) str2=”mnbmbmb”(错误)

  1. 引用数组元素

    char a[]=“123456!”;
    char *p=“12345!”;
    都可以用下标法和地址(指针)法引用数组元素
    a[5],*(a+5), p[5],*(p+5)


3 指针数组和数组指针

3.1 指针和数组的区别

  • 写一个简单的统计字符串字符个数的代码
#include <stdio.h>

int main()
{
    char str[] = "I love you";
    int count = 0; //用于统计

    while (*str++ != '\0')
    {
        count++;
    }

    printf("总共有%d个字符!\n", count);

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 运行结果
error: cannot increment value of type 'char [11]'
    while(*str++ != '\0')
           ~~~^
1 error generated.
  • 1
  • 2
  • 3
  • 4
  • 原因:我们以前学过,C语言的术语lvalue指用于识别或定位一个存储位置的标识符,是地址。rlvalue是值,可以是变量或表达式(注意:左值同时还必须是可改变的)

  • 由于数组名是字符串的领头羊,是个地址常量,无法被随意改变(进行自增等操作),不是lvalue,报错
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 修改:添加一个中间地址变量(可改变的)

#include <stdio.h>

int main()
{
    char str[] = "I love you";
    char *target = str;
    int count = 0;

    while (*target++ != '\0')  //(1)
    {
        count++;
    }

    printf("总共有%d个字符!\n", count);

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. 自增运算符++比取值运算符*优先级高,但后缀运算符有个特性,要在下一条语句中才会显示,就这一次虽然自增但是还是用的原来target值
    在这里插入图片描述
  1. 其实我们可以在while()内只有*target,把target++留在{}里面
  • 运行结果

总共有10个字符!

3.2 指针数组和数组指针的区别

  • 虽然有点绕,但聪明的小伙伴会发现一个是数组一个是指针
int *p1[5]; //指针数组
int (*p2)[5]; //数组指针
  • 1
  • 2

3.3 指针数组

  • 数组元素都为指针的数组
  • [ ]优先级比*高,所以是数组

int *p1[5]

下标01234
元素int*int*int*int*int*
  • 指向整型的指针
  • 示例
#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    int e = 5;
    int *p1[5] = {&a, &b, &c, &d, &e};
    int i;

    for(i = 0; i < 5; i++)
    {
        printf("%d\n", *p1[i]);
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 运行结果
1
2
3
4
5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 可以看出指针数组的一些东西,但心智正常的人不会这样写代码
  • 字符型指针数组,一串数据元素都是指针的数组。可以构造紧凑型字符串数组
#include <stdio.h>

int main()
{
    char *p1[] = {          //(1)
        "让c语言改变世界 -- 小c",
        "让python改变世界 -- 小p",
        "让Java改变世界 -- 小j",
        "让go改变世界 -- 小g",
        "让vb改变世界 -- 小v",
    };
    int i;

    for (i = 0; i < 5; i++)
    {
        printf("%s\n", p1[i]); //(2)
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  1. 如同整型指针数组,数组中每一个元素是一个指针,存储一串对应元素的地址。整型变量就是&a,字符串变量直接用字符串就行,自己(字符串名)就是字符串第一个字符的地址。
  2. *p1[]或*p1[5] 都行
  3. 为什么是p1[i]而不是*p1[i]
  • p1[i]:例如p1[0],是指针数组的第一个指针指向第一个字符串的首字符,%s输出会自己往后读后面字符,直到’\0’。
  • *p1[i]:例如*p1[1],是指针数组第一个指针指向第一个字符串的首字符,则配合%c只会输出每个字符串第一个字符,(只发现有数字和英文可以,中文不行不知道为啥。)

3.4 数组指针

建议先看第四大块指针和二维数组再看数组指针

  • int (*p2)[5]
  • 优先级:( )> [ ] > *
  • 先定义一个指针,再定义一个数组,相当于定义了一个首地址为p2的int数组。(对于C语言的二维数组来说,数组指针就是行指针)
  • 先来一个看起来正确实则错误的典型案例
#include <stdio.h>

int main()
{
    int (*p2)[5] = {1, 2, 3, 4, 5};
    int i;

    for ( i = 0; i < 5; i++)
    {
        printf("%d\n", *(p2 + i));
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 运行结果

3 warnings generated.
1
21
41
61
81

  • warning警告,我们发现可能因为p2是个指针变量,只能存放地址
  • 进行一个修改
#include <stdio.h>

int main()
{
    int temp[5] = {1, 2, 3, 4, 5};
    int (*p2)[5] = temp; //给它赋一个地址
    int i;

    for ( i = 0; i < 5; i++)
    {
        printf("%d\n", *(p2 + i));
    }

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 运行结果

2 warnings generated.
-523160112
-523160092
-523160072
-523160052
-523160032

  • 可恶啊,还是错误
  • 我们进行分析warning: incompatible pointer types initializing 'int (*)[5]' with an expression of type 'int [5]'; take the address with & [-Wincompatible-pointer-types]
  • 数组指针指向的是整个数组,一维数组数组名是指向单个int的指针,两边类型不相等(不在同一等级)所以报错
  • 正确代码
#include <stdio.h>

int main()
{
    int temp[5] = {1, 2, 3, 4, 5};
    int (*p2)[5] = &temp;  //(1)
    int i;

    for ( i = 0; i < 5; i++)
    {
        printf("%d\n", *(*p2 + i)); //(2)
        printf("%p\n", *p2 + i);
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 数组指针是真正指向数组的指针,而不是指向变量。把数组当作一个整体,取出他的地址,赋给数组指针才是数组指针想要的。
  2. (*p2)[5]=&temp,这里有&取地址,然后数组本身就是一个地址,所以这里等于是数组temp[5]里头元素的地址的地址 所以下面打印的时候要两次*
  • 运行结果

0x7ffeec0065c0
1
0x7ffeec0065c4
2
0x7ffeec0065c8
3
0x7ffeec0065cc
4
0x7ffeec0065d0
5

来自弹幕
[1.]最开始讲到*p = a; 指针p指向了a这个数组第一个元素的地址,那么它指向了数组本身了吗?没有!它指向的是数组的第一个元素!!
[2.]而数组指针指向的是这个数组的本身(数组首元素地址)!!这个数组指针指向的内存空间中,含有元素的地址!
[3.]元素的地址在数组地址指向的内存空间里面!


4 指针和二维数组

个人感觉学校里讲这块比小甲鱼清晰,
先上学校的ppt。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

在这里插入图片描述

小甲鱼所说的数组指针,其实在C语言二维数组中就是行指针(二维数组数组名),只不过跨度(+1时)是跨一行的,是指向行的。
而二维数组中指向元素的是列指针,跨度(+1时)是跨一个元素。
一维数组中,数组名是元素指针。
二维数组中,数组名是数组指针(行指针),比如a,a+1。而(a[0],a[1],a[0]+1,*a+1,*(a+1)+1)等是元素指针(列指针)
以上为个人理解和总结,仅供参考。
以下为小甲鱼课的笔记。

4.1 验证二维数组数组名指向什么

  • 验证一下二维数组的数组名指向什么
#include <stdio.h>

int main()
{
    int array[4][5] = {0};

    printf("sizeof int: %lu\n", sizeof(int));
    printf("array: %p\n", array);
    printf("array + 1: %p\n", array + 1);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 运行结果

sizeof int: 4
array: 0x7ffee0deb5b0
array + 1: 0x7ffee0deb5c4

  • 十六进制下跨度为14,十进制下就是20,即5个int
  • 结论:array就是指向包含五个元素的数组的指针
  • 定义了数组名就是定义了一个指针,指向第一个数组,该数组包含5个元素

4.2 *(array + 1)表示什么

在这里插入图片描述

  • 解引用:将一个取值运算符作用于一个地址变量,即把一个地址的值取出来
  • *(array + 1) == array[1] (语法糖:不定义一个新的语法,但是用便于理解的方式代替新语法,是另一种形式,便捷清晰)
#include <stdio.h>

int main()
{
    int array[4][5] = {0};
    int i, j, k = 0;

    for (i = 0;  i < 4; i++)
    {
        for (j = 0; j < 5; j++)
        {
            array[i][j] = k++;
        }
    }

    printf("*(array + 1): %p\n", *(array + 1));
    printf("array[1]: %p\n", array[1]);
    printf("&array[1][0]: %p\n", &array[1][0]);
    printf("**(array+1): %d\n", **(array+1));

    return 0;
}
//二维数组里面其实是放了一维数组,这样一维数组,对于二维数组来说就是元素,就像数值对于一维数组来说是一个元素
//一维里面 数组名如同指针变量的功能存放着第一个元素的地址
//二维数组 也可类似这样看 ,形式是 array【i】存放的也是地址
//二维数组就是指针数组里面有四个指向五个元素数组的数组指针(行指针)
//array[1]== &array[1][0]== **(array+1) 
  • 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
  • 运行结果

*(array + 1): 0x7ffeec53b5e4
array[1]: 0x7ffeec53b5e4
&array[1][0]: 0x7ffeec53b5e4
**(array+1): 5

*的作用:如果是最小跨度就取值,比如取出int。如果不是则缩小跨度,比如数组跨度转为int元素跨度。

4.3 *(*(array+1)+3)表示什么

在这里插入图片描述

  • 推测是第二行第四列的值
#include <stdio.h>

int main()
{
    int array[4][5] = {0};
    int i, j, k = 0;

    for (i = 0;  i < 4; i++)
    {
        for (j = 0; j < 5; j++)
        {
            array[i][j] = k++;
        }
    }

    printf("*(*(array+1)+3): %d\n", *(*(array+1)+3));
    printf("array[1][3]: %d\n", array[1][3]);

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 运行结果

*(*(array+1)+3): 8
array[1][3]: 8

  • 结论:*(*(array+1)+3) == array[1][3]
    在这里插入图片描述

在这里插入图片描述

梅开二度:
小甲鱼所说的数组指针,其实在C语言二维数组中就是行指针(二维数组数组名),只不过跨度(+1时)是跨一行的,是指向行的。
而二维数组中指向元素的是列指针,跨度(+1时)是跨一个元素。
一维数组中,数组名是元素指针。
二维数组中,数组名是数组指针(行指针),比如a,a+1。而(a[0],a[1],a[0]+1,*a+1,*(a+1)+1)等是元素指针(列指针)
以上为个人理解和总结,仅供参考。


4.4 数组指针和二维数组

初始化二维数组是可以偷懒的

  • int array[2][3] = {{0, 1, 2}, {3, 4 ,5}} ;
  • 可以写成 int array[][3] = {{0, 1, 2}, {3, 4 ,5}} ;
  • 但是一定要写列数

定义一个数组指针是这样的

  • int (*p)[3]//指向三个元素;
    那么请问如何解释下边语句
    int (*p)[3] = array;
#include <stdio.h>

int main()
{
    int array[2][3] = {{0, 1, 2}, {3, 4 ,5}};
    int (*p)[3] = array;

    printf("**(p+1):%d\n", **(p+1));
    printf("**(array+1):%d\n", **(array+1));
    printf("array[1][0]:%d\n",array[1][0] );

    printf("**((p+1)+2):%d\n", *(*(p+1)+2));
    printf("*(*(array+1)+2):%d\n", *(*(array+1)+2));
    printf("array[1][2]:%d\n",array[1][2] );

    return 0;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 运行结果

**(p+1):3
**(array+1):3
array[1][0]:3
**((p+1)+2):5
*(*(array+1)+2):5
array[1][2]:5


5 void指针和NULL指针

5.1 void 指针

void本来是无类型,定义变量时void a;会报错。但是void指针却很厉害

  • void指针我们称之为通用指针,也就是说可以指向任意类型的数据。任何类型的指针都可以赋值给void指针。
#include <stdio.h>

int main()
{
    int num = 1024;
    int *pi = &num;
    char *ps = "FishC";
    void *pv;

    pv = pi;
    printf("pi:%p, pv:%p\n", pi, pv);

    pv = ps;
    printf("ps:%p, pv:%p\n", ps, pv);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 运行结果

pi:0x7ffeeb0c162c, pv:0x7ffeeb0c162c
ps:0x104b41f96, pv:0x104b41f96

  • 说明void指针可以指向不同类型,但最好强制转换
  • 不要对void类型直接进行解引用,因为不知道void的类型,会导致编译器不知道跨度多少
#include <stdio.h>

int main()
{
    int num = 1024;
    int *pi = &num;
    char *ps = "FishC";
    void *pv;

    pv = pi;
    printf("pi:%p, pv:%p\n", pi, pv);
    printf("pv:%d\n", *(int *)pv); //强制转换

    pv = ps;
    printf("ps:%p, pv:%p\n", ps, pv);
    printf("pv:%s\n", (char *)pv);
    //%s会自己往下读直到'\0'

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 运行结果

pi:0x7ffee76a7624, pv:0x7ffee76a7624
pv:1024
ps:0x10855bf7a, pv:0x10855bf7a
pv:FishC

  • void指针能够支持不同类型指针的转换,但一般不用

5.2 NULL指针

空指针
#define NULL((void *)0)
当你还不知道要将指针初始化什么地址时,请将它初始化NULL;在对指针进行解引用时,先检查该指针是否为NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。防止出现野指针

#include <stdio.h>

int main()
{
    int *p1;
    int *p2 = NULL;

    printf("%d\n", *p1);//野指针
    printf("%d\n", *p2);//避免了野指针的危害,程序直接报错

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

5.2.1 NULL和NUL

  • 两者截然不同
  • NUL是空字符’\0’的意思,字符串结尾
  • NULL用于指针和对象,表示控制,指向一个不被使用的地址

6 指向指针的指针

6.1 定义

在这里插入图片描述

#include <stdio.h>

int main()
{
    int num = 520;
    int *p = &num; //指针p存放变量num地址
    int **pp = &p; //指针的指针pp存放指针p的地址

    printf("num:%d\n", num); //打印num的内容
    printf("*p:%d\n", *p); //打印p指向的地址里的内容
    printf("**pp:%d\n", **pp);//打印**pp的内容
    printf("&p; %p, pp: %p\n", &p, pp); //验证pp中存放的是否是指针p的地址,%p就是把值转化成十六进制输出
    printf("&num: %p, p: %p, *pp: %p\n", &num, p, *pp); //验证变量num的地址、指针p存放的内容、指针的指针pp所指的是不是同个东西

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 运行结果

num:520
*p:520
**pp:520
&p; 0x7ffeeddcf618, pp: 0x7ffeeddcf618
&num: 0x7ffeeddcf624, p: 0x7ffeeddcf624, *pp: 0x7ffeeddcf624

6.2 指针数组和指向指针的指针

#include <stdio.h>

int main()
{
    char *cBooks[] = {
        "<C程序设计语言>",
        "<C专家编程>",
        "<C和指针>",
        "<C陷阱和缺陷>",
        "<C Primer Plus>",
        "<C语言--从入门到入土>"};
    
    char **byWo; //定义为指向字符指针的指针变量
    char **woLoves[4]; //定义一个数组,存放着四个指向指针的指针
    int i;

    byWo = &cBooks[5]; //第五个字符串也是一个指针,它(指针)取地址的值就是指向指针的指针存放的值了
    woLoves[0] = &cBooks[0]; //两边要同一个级别,下面同理
    woLoves[1] = &cBooks[1]; 
    woLoves[2] = &cBooks[2]; 
    woLoves[3] = &cBooks[3]; 

    printf("wo的图书有: %s\n", *byWo);//%s要的是地址,会从第一个字符(*bywo指向字符串第一个字符)读下去
    printf("wo喜欢的图书有: \n");

    for (i = 0; i < 4; i++)
    {
        printf("%s\n", *woLoves[i]);
    }
    return 0;
}
  • 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
  • 运行结果

wo的图书有: <C语言–从入门到入土>
wo喜欢的图书有:
<C程序设计语言>
<C专家编程>
<C和指针>
<C陷阱和缺陷>

  • 结果:使用指向指针的指针来指向数组指针
    • 好处:
    • 避免重复分配内存
    • 只需要进行一处修改

6.3 数组指针和二维数组

6.3.1

  • 根据指针输出一维数组来模仿写一个指针的指针输出二维数组
#include <stdio.h>

int main()
{
    int array[3][4] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}};
    int  **p = array;
    int i, j;

    for (i = 0; i < 3; i++)
    {
        for (j =0; j< 4; j++)
        {
            printf("%2d", *(*(p+i)+j));
        }
        printf("\n");
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 运行结果

系统报错
int **p = array;
1 warning generated.

  • 当我们进行一个修改
#include <stdio.h>

int main()
{
    int array[3][4] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}};
    //int  **p = array;  注释这部分
    int i, j;

    for (i = 0; i < 3; i++)
    {
        for (j =0; j< 4; j++)
        {
            printf("%2d", *(*(array+i)+j)); //p改为array
        }
        printf("\n");
    }
    return 0;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 运行结果

0 1 2 3
4 5 6 7
8 91011

  • 发现可以运行,我们从编译器的角度来思考问题。我们不知道int **p 的跨度是多少,所以进行一个测试。我们在原有的基础上加上这来验证
    printf("p: %p, array: %p\n", p, array);
    printf("p+1:%p, array+1:%p\n", p+1, array+1);
  • 1
  • 2
  • 运行结果

p: 0x7ffeeb99f5f0, array: 0x7ffeeb99f5f0
p+1:0x7ffeeb99f5f8, array+1:0x7ffeeb99f600

  • 发现p跨度是4个字节,array是十六个字节(十六进制下为10)
  • 说明array刚好跨度为一行,int **p = array;,编译器没有把指向的array理解为一个二维数组,只把array理解给指向指针的指针赋的一个地址。
  • 相当于二维数组可以忽略行,但是不能忽略列
  • 怎么样才能正确操作呢?

6.3.2 用数组指针来访问二维数组

#include <stdio.h>

int main()
{
    int array[3][4] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}};
    int  (*p)[4] = array; //(1)
    int i, j;

    for (i = 0; i < 3; i++)
    {
        for (j =0; j< 4; j++)
        {
            printf("%2d", *(*(p+i)+j));
        }
        printf("\n");
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 运行结果

0 1 2 3
4 5 6 7
8 91011

  • 解释

(1)int (*p)[4] = array,p指向的仍然是一维数组,但着重考虑的是跨度问题,p的跨度是4*(sizeof int),四列。array是二维数组的首地址赋值给了p,那p+1就是第二行,再进行每一行的索引。
(2)用数组指针来访问二维数组,我们利用的就是p指向的是4个元素的一维数组,所以p的跨度就是4*4=16字节。而二维数组array+1,就是加上一个元素,也就是加上一行(因为二维数组是一位数组线性扩充)
(3)二者跨度一样所以可以成功

7 常量和指针

7.1 常量

  • 常量应该在这样:520, ‘a’, 3.14
  • 或者这样:

#define PRICE 520
#define A ‘a’

  • 还可以把变量改成只读不可修改的量

const int price = 520
const char a = ‘a’

7.2 指向常量的指针

  • const int *pc = &cnum;
#include <stdio.h>

int main()
{
    int num = 520;
    const int cnum = 880;
    const int *pc = &cnum;

    printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
    printf("*pc: %d, pc: %p\n", *pc, pc);
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 运行结果

cnum: 880, &cnum: 0x7ffeede46634
*pc: 880, pc: 0x7ffeede46634

  • 发现正确运行。如果添加*pc=1024这样改变指针指向的内容,因为是常量,会被判定error,自行尝试。
#include <stdio.h>

int main()
{
    int num = 520;
    const int cnum = 880;
    const int *pc = &cnum;

    // printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
    // printf("*pc: %d, pc: %p\n", *pc, pc);

    pc = &num;
    printf("num: %d, %num: %p\n", num, &num);
    printf("*pc: %d, pc: %p\n", *pc, pc);
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 运行结果

num: 520, num: 0x7ffee7794634
*pc: 520, pc: 0x7ffee7794634

  • 结果: 发现可以改变指针pc的地址,可以实现改变*pc。num不是常量,但可以被指向常量的指针指向。*pc = 1024会报错。但是num = 1024在打印*pc不会报错而且可以正常运行
  • 指向常量的指针总结:

(1)指针可以修改为指向不同的常量
(2)指针可以修改为指向不同的变量(1和2说明指针本身可以修改)
(3)可以通过解引用来读取指针指向的数据
(4)不可以通过解引用修改指针指向的数据

7.3 常量指针

  • 指向常量的指针本身是可以改变的,若要指针也不能改变,则可使用常量指针
#include <stdio.h>

int main()
{
    int num = 520;
    const int cnum = 880; // 指向常量的指针
    int * const p = &num; // 指针常量

    *p = 1024; //指针常量, 指向的量可以变,可运行
    printf("*p: %d\n", *p);

    p =  &cnum; //指针常量,存储的地址不可以被修改, 不运行
    printf("*p: %d\n", *p);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 常量指针,指针地址不能变,指向的量可以变
    • 指向非常量的常量指针(int * const p = #)
      • 指针自身(存储的地址)不可以被修改§
      • 指针指向的值可以被修改(*p)
    • 指向常量的常量指针(const int * const p = #)
      • 指针自身不可以被修改
      • 指针指向的值也不可以被修改
      • 不能改变p或者*p,但当num是变量时,还是可以通过直接改变num的值改变num
  • 指向常量的指针,指针地址可以变,但解引用指向的量不可以变

7.4 指向“指向常量的常量指针”的指针

累了,爷不学了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/392565
推荐阅读