C语言进阶之路-数组与指针

news2024/11/25 1:53:39

目录

一、学习目标

二、数组入门

基本概念

语法释义:

定义:

访问:

赋值:

字符数组

多维数组

数组万能拆解法

三、指针入门

内存地址

基地址

取址符

指针基础

指针的定义:

指针的赋值

指针的尺寸

四、数组进阶

数组名涵义

数组下标

字符串('\0')常量

零长数组

变长数组

五、指针进阶

char型指针

多级指针

指针万能拆解法

void型指针

野指针

空指针

指针的加减:

const 型指针

函数指针

总结


一、学习目标

  • 数组的基础入门
  • 指针的基础入门
  • 数组和指针的阶级

二、数组入门

基本概念

  • 逻辑:一次性定义多个相同类型的变量,并存储到一片连续的内存中
  • 示例:
数据类型  变量名 [ 数量 ] ;
int a[5];

  • 语法释义

    • a 是数组名,即这片连续内存的名称
    • [5] 代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素
    • int 代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组

定义:

int a [5] ;

访问:

         初始化:

只有在定义的过程中顺便赋值,称为初始化。只有在初始化的时候可以对这个数组进行连续赋值。

int  a [5] = {1,2,3,4,5} ;  // 初始化
int a [5] ;  // 可行,但是该数组中的数据内容是不确定的
int a[5] = {100,200,300,400,500,600}; // 警告,越界了,编译器会把越界部分直接放弃 600 
int a[ ] = {100,200,300}; // OK,自动根据初始化列表分配数组元素个数
int a [] ; // 【错误】数组在定义申请内存的过程中 【无法确定内存大小】 
int a[5] = {100,200,300}; // OK,只初始化数组元素的一部分,未初始化部分被默认设置为0

赋值:

a[0] = 6; a[1] = 7;

数组元素的引用

  • 存储模式:一片连续的内存,按数据类型分割成若干相同大小的格子
  • 元素下标:数组开头位置的偏移量

元素下标偏移量

  • 示例:
int a[5]; // 有效的下标范围是 0 ~ 4
a[0] = 1;
a[1] = 66;
a[2] = 21;
a[3] = 4;
a[4] = 934;

a[5] = 62; // 错误,越界了
a    = 10; // 错误,不可对【数组名】赋值 

字符数组

  • 概念:专门存放字符的数组,称为字符数组
  • 初始化与元素引用:
char s1[5] = {'a', 'b', 'c', 'd', 'e'};       // s1存放的是【字符序列】,非字符串
char s2[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; // s2存放了一个【字符串】

char s[6] = {"abcde"}; // 使用字符串直接初始化字符数组【字符串】
char s[6] =  "abcde" ; // 大括号可以省略【字符串】
char s[5] =  "abcde" ; // 【字符序列】

s[0] = 'A'; // 索引第一个元素,赋值为 'A'

多维数组

  • 概念:若数组元素类型也是数组,则该数组称为多维数组
  • 示例:
int a[2][3];

// 代码释义:
// 1, a[2]   是数组的定义,表示该数组拥有两个元素
// 2, int [3]是元素的类型,表示该数组元素是一个具有三个元素的整型数组

二维数组的初始化:

int  a[2][3] ; // 定义了二维数组但是没有进行初始化,因此数组中的数据是随机的。
int  a[2][3] = {  {1,2,3}  ,  {9,8,7} };  // 初始化整个数组
int  a[2][3] = {  {1,2}  ,  {9,8} };  // 初始化二维数组中的小素组的的时候可以不完全初始化
int  a[2][3] = { 1,2,3 ,9 , 8 , 7} ; // 初始化整个数组 (按顺序进行初始化)

int a[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 错误,越界了, 多出来的 {7,8,9} 会被编译器丢弃

int a[2][3] = {{1,2,3}, {4,5,6,7}};        // 错误,越界了 , 多出来的 7 会被编译器丢弃

int a[ ][3] = {{1,2,3}, {4,5,6}}; // OK,自动根据初始化列表分配数组元素个数 a[2]
int a[2][ ] = {{1,2,3}, {4,5,6}}; // [错误]  int  []  类型不完成无法正确分配内存空间
int a[2][3] = {{1,2,3}};          // OK,只初始化数组元素的一部分

打怪实战

  • 定义一个用于存放多个学生姓名的数组。
    • 访问数据
      • 键盘输入新的学生姓名
      • 遍历打印所有的学生姓名
    • 尝试对于他进行初始化
    • 基础练习:使用整形数组完成以上练习

数组万能拆解法

  • 任意的数组,不管有多复杂,其定义都由两部分组成。
    • 第1部分:说明元素的类型,可以是任意的类型(除了函数)
    • 第1部分:说明数组名和元素个数

  • 示例:
int   a[4];       // 第2部分:a[4]; 第1部分:int
int   b[3][4];    // 第2部分:b[3]; 第1部分:int [4]
int   c[2][3][4]; // 第2部分:c[2]; 第1部分:int [3][4]
int  *d[6];       // 第2部分:d[6]; 第1部分:int *
int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)

注解:

    • 上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]本质上并无区别,它们均是数组
    • 上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]唯一的不同,是它们所存放的元素类型的不同
    • 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

三、指针入门

内存地址

  • 字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bits
  • 地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址。

基地址

  • 单字节数据:对于单字节数据而言,其地址就是其字节编号。
  • 多字节数据:对于多字节数据而言,期地址是其所有字节中编号最小的那个,称为基地址(入口地址)。

取址符

  • 每个变量都是一块内存,都可以通过取址符 & 获取其地址
  • 例如:
int a = 100;
printf("整型变量 a 的地址是: %p\n", &a);

char c = 'x';
printf("字符变量 c 的地址是: %p\n", &c);

double f = 3.14;
printf("浮点变量 f 的地址是: %p\n", &f);

注意:

  • 内存地址的大小取决于系统的位数。
    • 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸却是一样的。
    • 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。
char *     p1;  // 尺寸为 8字节, 指向的内存应该是1个字节的
short *    p2;  // 尺寸为 8字节, 指向的内存应该是2个字节的
int *      p3;  // 尺寸为 8字节, 指向的内存应该是4个字节的
long *     p4;  // 尺寸为 8字节, 指向的内存应该是8个字节的

指针基础

  • 指针的概念:
    • 地址。比如 &a 是一个地址,也是一个指针,&a 指向变量 a。
    • 专门用于存储地址的变量,又称指针变量。

理解:

& 取地址符 --> 取得某一个变量的地址

* 解引用符 --> 去地址(去到某一个地址中访问数据)

语法:

指针指向的类型 * 指针变量名 ;

  • 指针的定义:

int    *p1; // 用于存储 int  型数据的地址,p1 被称为 int  型指针,或称整型指针
char   *p2; // 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针
double *p3; // 用于存储double型数据的地址,p3 被称为 double 型指针
  • 指针的赋值

    • 把某一个对应类型的地址存入到指针变量中,类型需跟指针的类型相匹配。
    • 指针变量专门用于存放地址
int a = 100;
p1 = &a; // 将一个整型地址,赋值给整型指针p1

char c = 'x';
p2 = &c; // 将一个字符地址,赋值给字符指针p2

double f = 3.14;
p3 = &f; // 将一个浮点地址,赋值给浮点指针p3
  • 指针的索引:通过指针,取得其指向的目标
*p1 = 200; // 将 p1 指向的目标(即a)修改为200,等价于 a = 200;
*p2 = 'y'; // 将 p2 指向的目标(即c)修改为'y',等价于 c = 'y';
*p3 = 6.6; // 将 p3 指向的目标(即f)修改为6.6,等价于 f = 6.6;

指针的尺寸

  • 指针尺寸指的是指针变量自己所占内存的字节数
  • 指针所占内存,取决于地址的长度,而地址的长度则取决于系统寻址范围,即字长
  • 结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关

小怪实战

  • 尝试实现随机点名
    • 【拓展】参考C语言课程的小项目效果进行拓展
    • 通过随机数产生一个数组下标然后把对应的名字输出
    • 从键盘中获取学生的姓名 (假设数组有10个元素则可以先在代码中写8个通过键盘获取剩余的两个)
    • 使用二维数组存储N个学生姓名
// 使用二维数组实现随机点名

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{

    char NameS[100][32] = {
        "Even",    // NameS[0]
        "Jacy",     // NameS[1]
        "TieZhu",
        "ErGou",
        "CuiHua",
        "DaChui",     // NameS[5]
        "ZhangSan" ,      // NameS[6]
        "Yilia",
        "WangDaChui",
        "TieDan",
    };


    // for (int i = 0; i < 3 ; i++)
    // {
    //     printf("请输入一个姓名:\n");
    //     scanf("%s" , NameS[i+7]);
    // }


    // 设置随机种子
    srand((int)time(0));
    int index ;
    
    // 每间隔0.3秒出现一个名字
    for (int i = 0; i < 10; i++)
    {
        // 产生随机数
        index = (int) (10.0 * rand() / (RAND_MAX + 1.0));
        // printf("index:%d\r" , index);   // 往标准输出中打印数据,因此如果没有\n则数据不会立即显示
        // fprintf( stderr , "\rindex:%d" , index  );
        fprintf( stderr ,"\r幸运观众:%s" , NameS[index]); // fprintf可以指定输出的目标文件 
            ///  stderr 则是标准出错文件,文件没有缓冲区 , 只要有数据则立即显示

        // 1s = 1000 ms  = 1000000us 

        usleep(300000);
        fprintf( stderr ,"\r                          " ); // 使用空格当前这一行数据

    }

    // 每间隔0.5秒出现一个名字
    for (int i = 0; i < 5; i++)
    {
        // 产生随机数
        index = (int) (10.0 * rand() / (RAND_MAX + 1.0));
        // printf("index:%d\r" , index);
        // fprintf( stderr , "\rindex:%d" , index  );
        fprintf( stderr ,"\r幸运观众:%s" , NameS[index]);

        // 1s = 1000 ms  = 1000000us 

        usleep(500000);
        fprintf( stderr ,"\r                          " );
    }
    

    // 每间隔1秒出现一个名字
    for (int i = 0; i < 3; i++)
    {
        // 产生随机数
        index = (int) (10.0 * rand() / (RAND_MAX + 1.0));
        // printf("index:%d\r" , index);
        // fprintf( stderr , "\rindex:%d" , index  );
        fprintf( stderr ,"\r幸运观众:%s" , NameS[index]);

        // 1s = 1000 ms  = 1000000us 

        sleep(1);
        fprintf( stderr ,"\r                          " );
    }

    fprintf( stderr ,"\r幸运观众:%s\n" , NameS[index]);

    
    
    return 0;
}
  • 假如有如下定义:int a[3][5]; 完成如下要求:
  • 用1种方法表示 a[2][3] 的地址。 : &a[2][3] *(a+2)+3
  • 用3种完全等价的方法表示 a[0][0] 的地址。
  • 用2种完全等价的方法表示 a[2][0] 的地址。
//- 用1种方法表示 a[2][3] 的地址。 :  &a[2][3]   *(a+2)+3
printf("&a[2][3]:%p\n" , &a[2][3] );
printf("*(a+2)+3:%p\n" ,*(a+2)+3 );


printf("**************************\n");

//- 用2种完全等价的方法表示 a[2][0] 的地址。
printf("&a[2][0]:%p\n" , &a[2][0] );
printf("a[2]:%p\n" , a[2] );
printf("&a[2]:%p\n" , &a[2] );
printf("*(a+2):%p\n" ,*(a+2) );

printf("**************************\n");

// - 用3种完全等价的方法表示 a[0][0] 的地址。
printf("&a[0][0]:%p\n" , &a[0][0] );
printf("a[0]:%p\n" , a[0] );
printf("&a[0]:%p\n" , &a[0] );
printf("*a:%p\n" ,*a );
printf("a:%p\n" ,a );
    • 分析下面的程序的执行结果。
#include <stdio.h>
int main(void)
{
    int a[] = {1, 2, 3, 4};
    int i, *p;
    for(i=0, p=a; i<4; i++, p++)
    {
        printf("%d %d\n", a[i], *p);
    }
    return 0;
}

1 1
2 2
3 3
4 4
  • 【拓展】编写一个函数,接收三个类型相同的整型数组 a、b 和 c,将 a 和 b 的各个元素的值相加,存放到数组 c 中。
  • 【拓展】尝试使用指针来访问一维数组、二维数组中的元素。

四、数组进阶

数组名涵义

  • 数组名有两个含义:
    • 第一含义是:整个数组
    • 第二含义是:首元素地址
  • 当出现以下情形时,那么数组名就代表整个数组:
    • 在数组定义中 int arr [3] ;
    • 在 sizeof 运算表达式中 sizeof(arr) ;
    • 在取址符&中 &arr
  • 其他任何情形下,那么数组名就代表首元素地址。即:此时数组名就是一个指向首元素的指针。
  • 示例:
int a[3];                  // 此处,a 代表整个数组
printf("%d\n", sizeof(a)); // 此处,a 代表整个数组,因此计算的结果是整个数组的大小
printf("%p\n", &a);        // 此处,a 代表整个数组,此处为整个数组的地址

int *p = a;       // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]  , 指针p 指向的是数组中第0个元素的地址
p = a + 1;        // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]  , 指针p 指向的是数组中第1个元素的地址
function(a);      // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] , 传递给函数function的数组中第0个元素的地址
scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

C语言只有在第一含义的场合下表现为数组,其他大部分场合都表现为首元素的地址,当数组表现为首元素地址时,实际上就是一个指向其首元素的指针。数组运算实际上就是指针运算。

数组下标

数组的下标实际上就是基于入口地址的偏移量。

int arr[10];
a[3]  --》 基于 a  往后面偏移 3个int 的数据
a[0]  
  • 数组下标实际上是编译系统的一种简写,其等价形式是:
a[i] = 100;  等价于  *(a+i) = 100;
  • 根据加法交换律,以下的所有的语句均是等价的:
  a[i] = 100;
*(a+i) = 100;
*(i+a) = 100;
  i[a] = 100;
  • 数组运算,等价于指针运算。

字符串('\0')常量

  • 字符串常量在内存中的存储(存储于常量区),实质是一个匿名数组
  • 匿名数组,同样满足数组两种涵义的规定
  • 示例:
printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
printf("%p\n", &"abcd");        // 此处 "abcd" 代表整个数组

printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址
char *p1 = "abcd";         // 此处 "abcd" 代表匿名数组的首元素地址
char *p2 = "abcd" + 1;     // 此处 "abcd" 代表匿名数组的首元素地址

零长数组

  • 概念:长度为0的数组,比如 int data[0];
  • 用途:放在结构体的末尾,作为可变长度数据的入口
  • 因为数组是唯一一个允许进行越界访问的接口
  • 示例:
struct node
{
    /* 结构体的其他成员 */
    // 成员1
    // 成员2
    // ... ...
    
    int   len;
    char *data[0];
};

// 给结构体额外分配 10 个字节的内存。
struct node *p = malloc(sizeof(struct node) + 10);
p->len = 10;

// 额外分配的内存可以通过 data 来使用
p->data[0] ~ p->data[9]

变长数组

  • 概念:定义时,使用变量作为元素个数的数组
  • 要点:变长数组仅仅指元素个数在定义时是变量,而绝指数组的长度可长可短实际上,不管是普通数组还是所谓的变长数组,数组一旦定义完毕,其长度则不可改变
    • 只有在定义之前数组的大小是不确定的, 一旦定义结束后数组的大小就固定下来,与变量不在有任何的关系
  • 示例:
int len = 5;
scanf("%d" , &len) ;//99
int a[len];  // 数组元素个数 len 是变量,因此数组 a 是变长数组
len = 99 ; // 不再影响数组的大小

int x = 2;
int y = 3;
int b[x][y]; // 数组元素个数 x、y 是变量,因此数组 b 是变长数组
int b[2][y]; // 数组元素个数 y 是变量,因此数组 b 是变长数组
int b[x][3]; // 数组元素个数 x 是变量,因此数组 b 是变长数组
  • 语法:变长数组不可初始化,即以下代码是错误的:
int len = 5;
int a[len] = {1,2,3,4,5}; // 数组 a 不可初始化

五、指针进阶

char型指针

char型指针实质上跟别的类型的指针并无本质区别,但由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。

  • 定义:
char *p = "abcd";  // 变量p存放的是匿名数组"abcd" 的入口地址 == ‘a’的地址

多级指针

  • 如果一个指针变量 p1 存储的地址,是另一个普通变量 a 的地址,那么称 p1 为一级指针
  • 如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针
  • 如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
  • 以此类推,p2、p3等指针被称为多级指针

  • 示例:
int a = 100;
int   *p1 = &a;  // 一级指针,指向普通变量
int  **p2 = &p1; // 二级指针,指向一级指针
int ***p3 = &p2; // 三级指针,指向二级指针

指针万能拆解法

  • 任意的指针,不管有多复杂,其定义都由两部分组成。
    • 第1部分:指针所指向的数据类型,可以是任意的类型
    • 第2部分:指针的名字

  • 示例:
char   (*p1);      // 第2部分:*p1; 第1部分:char; 
char  *(*p2);      // 第2部分:*p2; 第1部分:char *; 
char **(*p3);      // 第2部分:*p3; 第1部分:char **; 
char   (*p4)[3];  // 第2部分:*p4; 第1部分:char [3]; 
char   (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float); 
  • 注解:
  1. 上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
  2. 上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的数据类型不同
  3. 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

void型指针

int   * p  ; //  *p  把p所指向的内存中的二进制码通过【整型的规则】进行解析的到数据
char  * p1 ; //  *p1  把p1所指向的内存中的二进制码通过【字符型的规则】进行解析的到数据
float * p2 ; //  *p2  把p2所指向的内存中的二进制码通过【浮点的规则】进行解析的到数据
void  * p3 ; //  *p3  把p3所指向的内存中的二进制码通过 【不知道什么规则】 进行解析的到数据
            // 因此 *p3 是不合理的,没有具体的规则解析内存则无法解析 ---》 无法直接使用 void 指针
  • 概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针 , 一般用于函数的返回值。
  • 要点:
    1. void 型指针无法直接索引目标必须将其转换为一种具体类型的指针方可索引目标(转换类型后则有了具体解析内存的规则)
    2. void 型指针无法进行加减法运算 (指针的加减是根据指针的类型进行地址偏移操作)
  • void关键字的三个作用:
    1. 修饰指针,表示指针指向一个类型未知的数据。
    2. 修饰函数参数列表,表示函数不接收任何参数
    3. 修饰函数返回类型,表示函数不返回任何数据
  • 示例:
// 指针 p 指向一块 4 字节的内存,且这4字节数据类型未确定
void *p = malloc(4); // malloc (4) 在堆内存中申请4个字节的内存空间并返回

// 1,将这 4 字节内存用来存储 int 型数据
*(int *)p = 100; // 先使用(int *) 来强制类型转换把p的类型转为 int *。然后才能*解引用
printf("%d\n", *(int *)p);

// 2,将这 4 字节内存用来存储 float 型数据
*(float *)p = 3.14;
printf("%f\n", *(float *)p);

void *一般用于函数的返回值,比如:

        以上函数都是用于在堆中申请内存的操作接口,由于申请到的内存可能会用于存储任何类型的数据,因此在设计这类申请内存的函数的时候不应该明确具体内存的地址类型,否则可能需要设计N个返回不同类型的申请函数。

野指针

  • 概念:指向一块未知区域的指针,被称为野指针。野指针是危险的

  • 危害:
  1. 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
  2. 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
  • 产生原因:
  1. 指针定义之后,未初始化
  2. 指针所指向的内存,被系统回收
  3. 指针越界
  • 如何防止:
  1.  指针定义时,及时初始化
  2. 绝不引用已被系统回收的内存
  3. 确认所申请的内存边界,谨防越界

空指针

很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。

  • 概念:空指针即保存了零地址的指针,亦即指向零地址的指针
  • 示例:
// 1,刚定义的指针,让其指向零地址以确保安全:
char *p1 = NULL;
int  *p2 = NULL;
float *p3 = 0 ;

// 2,被释放了内存的指针,让其指向零地址以确保安全:
char *p3 = malloc(100); // a. 让 p3 指向一块大小为100个字节的内存
free(p3);               // b. 释放这块内存,此时 p3 相当于指向了一块非法内存
p3 = NULL;              // c. 让 p3 指向零地址

指针的加减:

  • 指针加法意味着地址向上(高地址)移动若干个目标
  • 指针减法意味着地址向下(低地址)移动若干个目标
  • 示例:
int  a = 100;
int *p = &a; // 指针 p 指向整型变量 a

int *k1 = p + 2; // 向上移动 2 个目标(2个int型数据)
int *k2 = p - 3; // 向下移动 3 个目标(3个int型数据)


    long long   a ;
    char * p = &a ;

    printf("p:%p\n" , p);

    printf("p+1:%p\n" , p+1);
    printf("p+2:%p\n" , p+2);

    printf("p-1:%p\n" , p-1);
    printf("p-2:%p\n" , p-2);
  • 总结:
    • 指针的加减运算时 它加减的目标 大小是以指针的类型为单位,
      • 如果指针指向的是一个整形地址则+1、-1的时候以 int 为单位进行加减,
      • 如果指针的类型是Long 类型则 则+1、-1的时候以 long 为单位进行加减
    • 指针的加减与指针所指向的数据本身的类型没有任何关系(比如以上代码a不管是什么类型都不影响p的运算)

拓展:

long long  ** p1 = &a ;
int  ** p2 ;
char ** p3 ;

以上代码中p是一个二级指针,因此可以理解为: *p 明确了变量p是一个指针,剩下的 long long * 表示该变量存储的类型是一个 long long类型的地址, 由于地址的大小在某一个系统中是固定的,随意这个指针p在进行加减运算的时候步伐与地址的大小相关。

以上 p1 \p2 \p3 加减时都是以8字节(64为系统 ) 、 4 字节 (32为系统)为单位。

const 型指针

  • const型指针有两种形式:①常指针 ②常目标指针
  1. 常指针const修饰指针本身,表示指针变量本身无法修改(指针的指向是固定的)。

  1. 常目标指针const修饰指针的目标,表示无法通过该指针修改其目标

  • 常指针在实际应用中不常见。
  • 常目标指针在实际应用中广泛可见,用来限制指针的读写权限
  • 示例:
int a = 100;
int b = 200;

// 第1中形式,const修饰p1本身,导致p1本身无法修改
int * const p1 = &a;   // p1 的指向无法被修改它将“永远”指向 a 的地址

// 【拓展】 从侧面修改p1的指向
int ** p4 = &p1 ;
*p4 = &b ;  // 可以通过二级指针来间接修改p3所指向的内容
printf("*p3: %d \n" , *p3);

// 第2中形式,const修饰p2的目标,导致无法通过p2修改a
int const *p2 = &a;  // 无法通过 p2 来修改 变量 a 的数据
const int *p2 = &a;

常目标常指针

const  int * const ptr = &a ;
ptr = &b ; // [不允许]
*ptr = 777 ; // [不允许]

函数指针

  • 概念:指向函数的指针,称为函数指针
  • 特点:函数指针跟普通指针本质上并无区别,只是在取址和索引时,取址符和星号均可省略
  • 语法:

返回值类型 (*p) (参数列表) ;

  • 示例:
void   f (int) // 函数 f 的类型是: void (int)
{

}


void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数

p = &f; // p 指向 f(取址符&可以省略)
p =  f; // p 指向 f

// 以下三个式子是等价的:
  f (666); // 直接调用函数 f
(*p)(666); // 通过索引指针 p 的目标,间接调用函数 f
 p (666); // 函数指针在索引其目标时,星号可以省略

注意:

    1. 函数指针是一类专门用来指向(存储)某种类型函数的指针(地址)。
    2. 函数的类型不同,所需要的函数指针也不同。
    3. 函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。

总结

        本文细讲了打怪路上的数组和指针的特点和消灭方法,各位只需认真学习,即可消灭它们。祝各位都可爬上C语巅峰,斩尽拦路小妖。

        本文参考 粤嵌文哥 的部分课件,经过整理和修改后发布在C站。如有转载,请联系本人

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1287870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快手数仓面试题附答案

题目 1 讲一下你门公司的大数据项目架构&#xff1f;2 你在工作中都负责哪一部分3 spark提交一个程序的整体执行流程4 spark常用算子列几个&#xff0c;6到8个吧5 transformation跟action算子的区别6 map和flatmap算子的区别7 自定义udf&#xff0c;udtf&#xff0c;udaf讲一下…

java:slf4j、log4j、log4j2、logback日志框架的区别与示例

文章目录 背景SLF4J - 简单日志门面:Log4j - 强大而古老的日志框架:Log4j2 - Log4j的升级版:Logback - Log4j的继任者:比较Springboot集成slf4j、log4j2参考 背景 在Java开发中&#xff0c;日志记录是一个不可或缺的组成部分。为了满足不同的需求&#xff0c;Java社区涌现出多…

the name of a constructor must match the name of the enclosing class

构造器名匹配封闭类名 命令码的位置关系不对 解决&#xff1a;调整 命令码所在层级

探索人工智能领域——每日20个名词详解【day9】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

基于高德API实现网络geoJSON功能(整体)

代码实现&#xff1a; <script>// 3、初始化一个高德图层const gaode new ol.layer.Tile({title: "高德地图",source: new ol.source.XYZ({url: http://wprd0{1-4}.is.autonavi.com/appmaptile?langzh_cn&size1&style7&x{x}&y{y}&z{z},w…

Axure网页端高复用组件库, 下拉菜单文件上传穿梭框日期城市选择器

作品说明 组件数量&#xff1a;共 11 套 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;web端原型设计、桌面端原型设计 作品特色 本作品为「web端组件库」&#xff0c;高保真高交互 (带仿真功能效果)&#xff1b;运用了动态面板、中继…

Hadoop学习笔记(HDP)-Part.09 安装OpenLDAP

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

CUDA简介——Grid和Block内Thread索引

1. 引言 前序博客&#xff1a; CUDA简介——基本概念CUDA简介——编程模式CUDA简介——For循环并行化 Thread Index&#xff1a; 每个Thread都有其thread index。 在Kernel中&#xff0c;可通过内置的threadIdx变量来获取其thread index。threadIdx为三维的&#xff0c;有相…

nodejs+vue+微信小程序+python+PHP在线购票系统的设计与实现-计算机毕业设计推荐

伴随着信息时代的到来&#xff0c;以及不断发展起来的微电子技术&#xff0c;这些都为在线购票带来了很好的发展条件。同时&#xff0c;在线购票的范围不断增大&#xff0c;这就需要有一种既能使用又能使用的、便于使用的、便于使用的系统来对其进行管理。在目前这种大环境下&a…

电子学会C/C++编程等级考试2023年03月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最佳路径 如下所示的由正整数数字构成的三角形: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最…

ATECLOUD电源自动测试系统打破传统 助力新能源汽车电源测试

随着新能源汽车市场的逐步扩大&#xff0c;技术不断完善提升&#xff0c;新能源汽车测试变得越来越复杂&#xff0c;测试要求也越来越严格。作为新能源汽车的关键部件之一&#xff0c;电源为各个器件和整个电路提供稳定的电源&#xff0c;满足需求&#xff0c;确保新能源汽车的…

JSON 语法详解:轻松掌握数据结构(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

《微信小程序开发从入门到实战》学习四十一

4.3 云开发文件存储 文件存储功能支持将任意数量和格式的文件&#xff08;如图片和视频&#xff09;保存在云端&#xff0c;支持 以文件夹的形式将文件归类。 在云开发控制台中&#xff0c;可以对云端保存的文件进行管理。 也可以通过文件存储API对文件进行上传、删除、移动…

Android Chips(标签)

目录 一、流式布局标签发展历程 二、类型及使用 2.1 Chip.Action(默认值) 2.2 Chip.Entry 2.3 Chip.Filter 2.4 Chip.Choice 三、常用事件 3.1 OnClickListener 3.2 OnCheckedChangeListener 3.3 OnCloseIconClickListener 四、ChipGroup 4.1 ChipGroup Chip.Choi…

java开发神器之ecplise的基本使用

java开发神器之ecplise的基本使用 一、ecplise的安装二、利用ecplise创建工作空间 一、ecplise的安装 免安装eclipse程序包 二、利用ecplise创建工作空间 1、准备好eclipse的程序包&#xff0c;右键执行程序。 2、若打开eclipse显示如下第一张图的界面提示&#xff0c;是因…

leetcode 1004. 最大连续1的个数 III(优质解法)

代码&#xff1a; class Solution {public int longestOnes(int[] nums, int k) {int lengthnums.length;int zero0; //计数器&#xff0c;计数翻转 0 的个数int max0; //记录当前获得的最长子数组长度for(int left0,right0;right<length;right){if(nums[right]0){zero;wh…

scipy笔记:scipy.interpolate.interp1d

1 主要使用方法 class scipy.interpolate.interp1d(x, y, kindlinear, axis-1, copyTrue, bounds_errorNone, fill_valuenan, assume_sortedFalse) 2 主要函数 x一维实数值数组&#xff0c;代表插值的自变量y N维实数值数组&#xff0c;其中沿着插值轴的 y 长度必须等于 x 的…

根文件系统的开机自启动测试

一. 简介 本文在之前制作的根文件系统可以正常运行的基础上进行的&#xff0c;继上一篇文章地址如下&#xff1a; 完善根文件系统-CSDN博客 在前面测试软件hello 运行时&#xff0c;都是等 Linux 启动进入根文件系统以后手动输入 “./hello” 命令 来完成的。 我们一般做好产…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之文件管理(2)》(23)

《Linux操作系统原理分析之文件管理&#xff08;2&#xff09;》&#xff08;23&#xff09; 7 文件管理7.3 文件的目录结构7.3.1 文件说明、目录文件7.3.2 文件目录结构 7.4 文件存取与操作 7 文件管理 7.3 文件的目录结构 7.3.1 文件说明、目录文件 文件说明 FCB&#xff…

dockerdesktop 制作asp.net core webapi镜像-连接sqlserver数据库容器

1.使用visual studio 创建 asp.net core webapi项目 选择启用docker 会生成Dockerfile文件 2.使用efcore连接数据库&#xff0c;安装efcore的包 <ItemGroup><PackageReference Include"Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version&qu…