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

目录

一、学习目标

二、数组入门

基本概念

语法释义:

定义:

访问:

赋值:

字符数组

多维数组

数组万能拆解法

三、指针入门

内存地址

基地址

取址符

指针基础

指针的定义:

指针的赋值

指针的尺寸

四、数组进阶

数组名涵义

数组下标

字符串('\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站。如有转载,请联系本人

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Vue】将官方路由管理器 vue-router 库引入 Vue 项目的三种方法

前言 Vue Router 是 Vue.js 的官方路由管理器。它可以帮助我们在 Vue 应用中实现页面之间的跳转和导航&#xff0c;并且提供了一些高级功能&#xff0c;如路由参数、路由嵌套、路由守卫等。 Vue Router 的主要作用是将不同的组件映射到不同的 URL&#xff0c;并根据 URL 的变化…

数据库学习日常案例20231203-Mysql高级 -- 日志管理篇

Mysql高级 -- 日志篇 *日志类型 1.mysql的6类日志&#xff1a; 2.日志的弊端 *慢查询日志(slow query log) *通用查询日志(general query log) 1.作用&#xff1a; 2.问题场景&#xff1a; 3.查看当前状态 &#xff1a; 4.启动日志&#xff1a; 方式1&#xff1a;永久…

根文件系统中文字符测试

一. 简介 本文在之前制作的根文件系统可以正常运行的基础上进行的&#xff0c;继上一篇文章地址如下&#xff1a; 根文件系统初步测试-CSDN博客 本文测试根文件系统的是否可以支持中文字符。 二. 根文件系统中文字符测试 1. 创建中文文件 打开 ubuntu虚拟机&#xff0c;进…

JVM虚拟机:JVM参数之X参数

本文重点 本文将学习x参数 x参数 -Xint:解释执行 -Xcomp&#xff1a;第一次使用就编译成本地代码 -Xmixed&#xff1a;混合模式&#xff08;Javac、java&#xff0c;先编译后执行&#xff09;

Unity加载配置文件【解析Json】

Json 文件 Json文件的存储&#xff1a; 存储在StreamingAssets目录下的&#xff1a;//这里用了游戏配置表常用的Json存储格式-对象数组 {"data":[{"id": 1001,"name": "ScreenFront_1",},{"id": 1002,"name": &…

【QT】Qt常用数值输入和显示控件

目录 1.QAbstractslider 1.1主要属性 2.QSlider 2.1专有属性 2.2 常用函数 3.QScrollBar 4.QProgressBar 5.QDial 6.QLCDNumber 7.上述控件应用示例 1.QAbstractslider 1.1主要属性 QSlider、QScrollBar和Qdial3个组件都从QAbstractSlider继承而来&#xff0c;有一些共有的属性…

StackGres 1.6,可私有部署的云原生数据库中间件平台工程

StackGres 数据库平台工程简介 Enterprise Postgres made easy. On Kubernetes StackGres 是 Kubernetes 的全栈 PostgreSQL 发行版&#xff0c;打包成一个简单的部署单元。 使用精心选择和调优的 PostgreSQL 组件。 一个企业级的 PostgreSQL 栈需要几个其他的生态系统组件和重…

4 STM32MP1 Linux系统启动过程

1. ROM代码 这是ST官方写的代码&#xff0c;在STM32MP1出厂时就已经烧录进去&#xff0c;不能被修改。ROM代码是上电以后首先执行的程序&#xff0c;它的主要工作就是读取STM32MP1的BOOT引脚电平&#xff0c;然后根据电平来判断当前启动设备&#xff0c;最后从选定的启动设备里…

案例048:基于微信小程序电影订票系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

Navicat 与 华为云 GaussDB 合作再升级,赋能 GaussDB 分布式数据库

2023 年第三季度&#xff0c;Navicat 首次支持了华为云 GaussDB 主备版数据库。经过双方团队进一步的深化合作&#xff0c;Navicat 完成了 GaussDB 分布式的研发适配工作&#xff0c;赋能 GaussDB 全域数据库产品。 GaussDB 数据库分为主备版和分布式版两种模式。主备版适用于…

【React】使用react hooks实现评论示例

实现功能 1、渲染评论列表 2、删除评论 3、渲染导航栏和高亮 4、评论列表排序功能 5、获取评论 6、点击发布按钮发布评论 7、清空输入框 8、重新聚焦 实现代码 1、需要引入 import React, { useRef, useState } from react import avatar from "../logo.png" //头…

Linux socket编程(11):Unix套接字编程及通信例子

Unix套接字是一种用于在同一台计算机上的进程间通信的一种机制。它是Linux和其他类Unix系统中的一项特性&#xff0c;通过在文件系统中创建特殊的套接字文件&#xff0c;进程可以通过这些套接字文件进行通信。 文章目录 1 Unix和TCP套接字对比2 Unix套接字初始化流程3 例:服务端…

【springboot】整合redis和定制化

1.前提条件:docker安装好了redis,确定redis可以访问 可选软件: 2.测试代码 (1)redis依赖 org.springframework.boot spring-boot-starter-data-redis (2)配置redis &#xff08;3&#xff09; 注入 Resource StringRedisTemplate stringRedisTemplate; 这里如果用Autowi…

HarmonyOS学习--了解基本工程目录

1.工程级目录 工程的目录结构如下&#xff1a; 其中详细如下&#xff1a; AppScope中存放应用全局所需要的资源文件。entry是应用的主模块&#xff0c;存放HarmonyOS应用的代码、资源等。oh_modules是工程的依赖包&#xff0c;存放工程依赖的源文件。build-profile.json5是工…

FPGA实现电机位置环、速度环双闭环PID控制

一、设计思路 主要设计思路就是根据之前写的一篇FPGA实现电机转速PID控制&#xff0c;前面已经实现了位置环的控制&#xff0c;思想就是通过电机编码器的当前位置值不断地修正PID去控制速度。 那为了更好的实现控制&#xff0c;可以在位置环后加上速度环&#xff0c;实现电机位…

【S32K3环境搭建】-0.2-安装S32DS product updates和 packages

目录 1 安装S32DS product updates和 packages 1.1 方法一&#xff1a;通过S32DS Extensions and Updates安装product updates和 packages 1.2 方法二&#xff1a;通过Install New Software…安装product updates和 packages 2 S32DS product updates和 packages安装后的效…

1-4节电池升降压充电IC解决方案

描述 MP2760是一款集成窄电压DC&#xff08;NVDC&#xff09;电源路径管理功能和USB On-the-Go(OTG)功能的升降压充电IC&#xff0c;兼容USB PD&#xff0c;适用于单节至4节串联的电池包应用。该芯片的充电输入电压范围广&#xff0c;可支持最高22V。 当启用电池放电模式&…

网络安全威胁——中间人攻击

中间人攻击 1. 定义2. 中间人攻击如何工作3. 常见中间人攻击类型4. 如何防止中间人攻击 1. 定义 中间人攻击&#xff08;Man-in-the-Middle Attack&#xff0c;简称MITM&#xff09;&#xff0c;是一种会话劫持攻击。攻击者作为中间人&#xff0c;劫持通信双方会话并操纵通信过…

Java数据结构之《最短路径》(难度系数100)

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度偏难(偏难理解)的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题…

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…