首页 > 其他分享 >C数组和指针

C数组和指针

时间:2023-05-25 16:22:29浏览次数:25  
标签:int 2023 数组 printf total 指针

C数组和指针

  1. 关键字 -> static
  2. 运算符 -> &*
  3. 创建并初始化数组
  4. 指针、指针和数组的关系
  5. 编写处理数组的函数
  6. 二维数组

数组

什么是数组?

数据类型相同的一系列元素

声明数组的方式:

多少个元素->数组大小

元素的类型

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/6/2023, 4:44:33 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/6/2023, 4:44:33 PM
 * Description: 声明数组,初始化数组,打印数组节点
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define MONTHS 12

int main(void)
{
    // 声明数组记录一个月的天数
    int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int index;

    for (index = 0; index < MONTHS; index++)
        printf("%d 月的天数是 %d.\n", index + 1, days[index]);
    
    getchar();

    return 0;
}

注意:

  1. 只读数组,数组声明并且初始化以后值无法修改
  2. 数组声明如果未初始化会存在初始化失败的问题

只读数组:

/**
 * @Author: Lucifer
 * @Date: 5/6/2023, 4:55:33 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/6/2023, 4:55:33 PM
 * Description: 只读数组的声明、初始化、使用
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define MONTHS 12

int main(void)
{
    // 声明数组记录一个月的天数
    const int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int index;

    for (index = 0; index < MONTHS; index++)
        printf("%d 月的天数是 %d.\n", index + 1, days[index]);
    
    getchar();

    return 0;
}

数组部分初始化:

/**
 * @Author: Lucifer
 * @Date: 5/6/2023, 5:02:11 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/6/2023, 5:02:11 PM
 * Description: 部分初始化数组
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define SIZE 4

int main(void)
{
    int some_days[SIZE] = {1492, 2023};
    int index;

    for (index = 0; index < SIZE; index++)
        printf("%2d %14d\n", index + 1, some_days[index]);
    
    getchar();
    
    return 0;
}

指定初始化元素:

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/6/2023, 5:10:42 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/6/2023, 5:10:42 PM
 * Description: c99方式指定初始化数组元素
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define MONTHS 12

int main(void)
{
    int days[MONTHS] = {31, 28, [4] = 31}; // 注意数组越界问题,在指定初始化的时候 -> 部分编译器不支持c99方式去初始化数组元素
    int index;

    for (index = 0; index < MONTHS; index++)
        printf("%2d %d\n", index + 1, days[index]);
    
    getchar();

    return 0;
}

未指定初始化数组大小,初始化特定元素初值:

/**
 * @Author: Lucifer
 * @Date: 5/6/2023, 5:13:39 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/6/2023, 5:13:39 PM
 * Description: 未指定初始化数组大小,初始化特定元素初值
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>

int main(void)
{
    int days[] = {31, [6] = 31};
    int index;

    for (index = 0; index < sizeof days; index++)
        printf("%2d %12d", index + 1, days[index]);
    
    getchar();

    return 0;
}

注意:

  • 数组中数组大小可以是整型常量表达式 -> 例如[sizeof(days)][5*2 + 10]

二维数组

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/6/2023, 5:55:18 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/6/2023, 5:55:18 PM
 * Description: 二维数组的初始化以及使用
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define MONTHS 12
# define YEARS 5

int main(void)
{
    // 声明二维数组并初始化--->初始化方法:先初始化一维数组,在初始化二维数组
    const float rain[YEARS][MONTHS]= 
    {
        {4.3, 4.3, 4.3, 3.0, 3.0, 3.0, 2.5, 2.5, 2.5, 1.0, 1.0, 1.0},
        {3.0, 3.0, 3.0, 4.3, 4.3, 4.3, 1.0, 1.0, 1.0, 2.5, 2.5, 2.5},
        {2.5, 2.5, 2.5, 3.0, 3.0, 3.0, 4.3, 4.3, 4.3, 1.0, 1.0, 1.0},
        {1.0, 1.0, 1.0, 4.3, 4.3, 4.3, 3.0, 3.0, 3.0, 2.5, 2.5, 2.5},
        {4.3, 4.3, 4.3, 2.5, 2.5, 2.5, 1.0, 1.0, 1.0,3.0, 3.0, 3.0}
    };

    int year, month;
    float subtot, total;

    printf("YEARS    RAINFALL (inches)\n");
    for (year = 0, total = 0; year < YEARS; year++) // 每一年各月降水量总和
    {
        for (month = 0, subtot = 0; month < MONTHS; month++)
            subtot += rain[year][month];
        printf("%5d %15.1f\n", 2015 + year, subtot);
        total += subtot; // 5年总降水量
    }
    
    for (month = 0; month < MONTHS; month++) // 每个月五年的降水量总和
    {
        for (year = 0, subtot = 0; year < YEARS; year++)
            subtot += rain[year][month];
        printf("%4.1f", subtot / YEARS);
    }

    printf("\n");

    getchar();
    
    return 0;
}

指针和数组

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/10/2023, 10:00:25 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/10/2023, 10:00:25 PM
 * Description: 指针在数组当中的使用
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define SIZE 4

int main(void)
{
    short dates[SIZE];
    short * pti; // 这是一个指针变量 -> 值是一个地址
    short index;
    double bills[SIZE];
    double * ptf;

    pti = dates;
    ptf = bills; // 由此可以看到,数组是一串连续的内存地址
    printf("%23s %15s\n", "Shoot", "double");
    for ( index = 0; index < SIZE; index++)
        printf("索引的指针是 + %d: %10p, %10p\n", index, pti + index, ptf + index); // 从第二行开始就是每一个数组索引的值的地址

    getchar();

    return 0;
}

注意:

  • short类型占用2字节,double类型占用8字节
  • 在数组当中,指针+1是指下一个元素的地址
  • 光知道指针无用,还要知道变量的类型,否则*无法正确的取回地址上的值

所以再上诉示例当中,打印出来的结果是short类型每一个地址之间差2,double类型每一个地址之间差8

总结概括指针的特性:

  • 指针的值是它所指向的对象的地址,地址的表示方法依赖于计算机内部硬件.许多计算机都是按字节编码,内存中的每一个字节都按顺序编号

  • 指针前使用*运算符可以得到该指针所指向对象的值(指针的值是一个十六进制数)

  • 指针+1,指针的值递增它所指向类型的大小(以字节为单位)

举例:

*(ar + n); // 表示到内存地址ar位置,然后移动n个单元,检索存储在那里的值

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/10/2023, 10:18:30 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/10/2023, 10:18:30 PM
 * Description: 使用指针表示数组的值
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define MONTHS 12

int main(void)
{
    int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int index;

    for (index = 0; index < MONTHS; index++)
        printf("%2d 月, 有 %d 天\n", index + 1, *(days + index)); // 这里的index+1对于16进制地址来说就是下一个元素的地址
    // *(days + index) == days[index];
    
    getchar();

    return 0;
}

函数、数组、指针

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/10/2023, 10:56:18 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/10/2023, 10:56:18 PM
 * Description: 使用指针和数组分别进行求和计算
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# include<stdbool.h> // 引用bool
# define MONTHS 12
int SumByPot(int * p, int);
int SumByArr(int [], int);

int main(void)
{
    int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int totalByPot;
    int totalByArr;

    totalByPot = SumByPot(days, 10);
    totalByArr = SumByArr(days, 10);

    if (totalByPot == totalByArr)
        printf("%d", true);
    else
        printf("pot: %d, arr: %d.\n", totalByPot, totalByArr);

    getchar();

    return 0;
}

int SumByPot(int * pi, int n)
{
    int i;
    int total = 0;

    for (i = 0; i < n; i++)
        total += *(pi + i);

    return total;
}

int SumByArr(int ar[], int n)
{
    int i;
    int total = 0;

    for (i = 0; i < n; i++)
        total += ar[i];
    
    return total;
}

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/10/2023, 11:04:40 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/10/2023, 11:04:40 PM
 * Description: 使用指针求和计算字节大小
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define SIZE 10
int sum(int ar[], int n);

int main(void)
{
    int marbles[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20}; // marbles内包含10个int类型的数值,每个int类型4字节,所以marbles是40字节大小
    long answer;

    answer = sum(marbles, SIZE);
    printf("结果总和: %ld.\n", answer);
    printf("marbles的字节大小是 %zd 字节.\n", sizeof(marbles));

    getchar();

    return 0;
}

int sum(int ar[], int n)
{
    int i;
    int total = 0;

    for ( i = 0; i < n; i++)
        total += ar[i];

    printf("该数组占 %zd 字节.\n", sizeof(ar)); // ar并不是数组本身,只是一个指向数组首元素的地址,系统中用8字节存储地址,所以是8字节
    
    return total;
}

总结:

  • 声明数组中的变量代表的是数组内所有元素的集合 -> 参考marbles
  • 形参中传的数组实际上并不是数组,而只是指向数组中首元素的指针 -> 参考ar的大小

双指针示例:

/**
 * @Author: Lucifer
 * @Date: 5/10/2023, 11:17:45 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/10/2023, 11:17:45 PM
 * Description: 使用双指针进行数组求和 -> 这里面要注意声明出的数组的类型
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define SIZE 10
int SumByDP(int * start, int * endl);

int main(void)
{
    int marbles[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20}; // 注意marbles声明出来并不是int类型,而是*int指针类型 -> 声明一个指针,该指针占用10个int类型元素大小的字节
    long answer;

    answer = SumByDP(marbles, marbles + SIZE);
    printf("总和是: %ld.\n", answer);

    getchar();

    return 0;
}

int SumByDP(int * start, int * endl)
{
    int total = 0;
    
    while (start < endl)
    {
        total += *start;
        start++;
    }

    return total;
}

注意:

  • 数组声明出来的时候只是数组类型的指针

  • 声明出来的数组大小是该类型的元素的长度的字节总和

  • 注意start的写法:total += *start++ -> 由于结合律从右往左计算,所以先累加值,在递增指针,而total += *(start++),这样则不会递增指针的值,始终指向同一个地址,但是该地址的值发生了变化 -> 这就是指针不安全

指针表示法以及数组表示法:

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/10/2023, 11:43:59 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/10/2023, 11:43:59 PM
 * Description: 对指针进行组合运算,观察指针指向的变化
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
int data[2] = {100, 200};
int moredata[2] = {300, 400}; // 时刻注意这样声明出来的是指向数组头元素的指针

int main(void)
{
    int * p1, *p2, *p3;

    p1 = p2 = data; // 因为data只是指向数组头元素的指针,所以可以这样赋值
    p3 = moredata;
    
    printf("p1: %d, p2: %d, p3: %d.\n", *p1, *p2, *p3); // p3内含有两个int类型元素,每个4字节
    printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d.\n", *p1++, *++p2, (*p3)++); // 先赋值在递增地址
    printf("*p1 = %d, *p2 = %d, *p3 = %d.\n", *p1, *p2, *p3); // 这里的p1是上面递增地址后的值,要时刻注意,上面地址的修改会影响下面的输出
    // 这里的p3还是原来的地址,但是地址上的值发生了变化

    getchar();

    return 0;
}

指针操作

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/12/2023, 10:09:08 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/12/2023, 10:09:08 PM
 * Description: 四则运算计算指针的变化以及指针地址的变化
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>

int main(void)
{
    int urn[5] = { 100, 200, 300, 400, 500 };
    int * ptr1, * ptr2, * ptr3;

    /** 使用前两个指针记录第一第二个数 */
    ptr1 = urn;
    ptr2 = &urn[2]; // 因为是指针变量,存储地址,所以用取址符

    printf("指针的值, 地址指向的值, 指针地址.\n");
    printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p.\n", ptr1, *ptr1, &ptr1); // &ptr1是指向指针的指针

    // 指针加法
    ptr3 = ptr1 + 4;
    printf("指针的值, 地址指向的值, 指针地址.\n");
    printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d.\n", ptr1 + 4, *(ptr1 + 4));
    ptr1++;
    
    printf("指针的值, 地址指向的值, 指针地址.\n");
    printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p.\n", ptr1, *ptr1, &ptr1); // 可以观察到指针ptr1指向的地址发生了变化,但是指向ptr1的指针的地址没有发生变化

    ptr2--;
    printf("指针的值, 地址指向的值, 指针地址.\n");
    printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p.\n", ptr2, *ptr2, &ptr2);

    --ptr1;
    ++ptr2;

    printf("重置以后指针的值:\n");
    printf("ptr1: %p, ptr2: %p.\n", ptr1, ptr2);

    // 观察指针之差
    printf("ptr1 - ptr2: %td.\n", ptr1 - ptr2);

    // 指针减去一个整数
    int * tempPtr = ptr3 - 2;
    printf("ptr3: %p, *ptr3 : %d ptr3 - 2 : %p, *tempPtr : %d.\n", ptr3, *ptr3, ptr3 - 2, *tempPtr);

    getchar();

    return 0;
}

注意:

  • 指针与整数的加减
  • 指针与指针的加减

指针与整数的加减:

  1. 减法当中指针必须是第一个运算对象
  2. 无论是加法还是减法,整数都会转成对于类型的字节(例如上诉例子,int类型是4字节,所以指针在进行加减法运算的时候实际上是加减掉:整数*4字节的数)
  3. 所以指针的加减法会在数组当中会移动到下一个对应的元素当中
  4. 数组越界问题指针可能会得出一个值或者运行错误

指针与指针的加减:

  1. 指针与指针之间的加减,计算的是同一个数组当中的两个元素的和和差

  2. 减法当中的结果会是差值/类型字节的整数,上诉代码当中,两个int类型的指针相减,结果/4因为int类型是4字节,所以结果是2

  3. 两个不同数组的指针进行加减运算,可能会得出一个值,也可能会导致运行时错误

编译器不会检查指针是否指向数组元素

不要解引用未初始化的指针:

示例代码:

int * p; // 未初始化的指针
*p = 5; // 不可以这样解引用

原因分析:

  1. 这样做将不知道5存储在何处,可能不会出什么错误,或者导致程序崩溃
  2. 创建指针时,系统只分配了存储指针本身的内存,未分配存储数据的内存
  3. 使用指针之前,必须先用分配的地址初始化它

数组中数据的保护

C代码当中,传递指针效率是最高的,所以有可能会导致原始数据被破坏

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/12/2023, 10:09:35 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/12/2023, 10:09:35 PM
 * Description: 通过指针安全修改数组当中的每一个值
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
void addTo(double ar[], int n, double val);

int main(void)
{
    double data[2] = {100.00, 200.00};

    for (int i = 0; i < 2; i++)
        printf("数组索引 %d 的值是 %.lf.\n", i, data[i]);
    
    // 调用函数修改数组中每个值
    addTo(data, 2, 2.50);

    for (int i = 0; i < 2; i++)
        printf("更新后数组索引 %d 的值是 %.lf.\n", i, data[i]);

    getchar();
    
    return 0;
}

void addTo(double ar[], int n, double val) // 该函数通过指针直接使用了原始数据
{
    int i;

    for (i = 0; i < n; i++)
        ar[i] += val;
}

可以使用const关键字保护数组,这里const并不是指数组必须是常量,而是不可修改(只读变量)

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/12/2023, 10:59:27 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/12/2023, 10:59:27 PM
 * Description: 使用const使得数组安全
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define SIZE 5
void showArray(const double ar[], int n);
void multArray(double ar[], int n, double mult);

int main(void)
{
    double dip[SIZE] = { 20.0, 17.66, 8.2, 15.3, 22.22 };
    int index;

    printf("输入希望使用的功能,或者按q退出:\n");
    printf("1. 展示数组当中的数          2. 数组当中的元素乘以一个数在展示.\n");
    while (scanf("%d", &index) == 1)
    {
        switch (index)
        {
        case 1:
            showArray(dip, SIZE);
            break;
        case 2:
            printf("输入要乘以几:\n");
            int temp;
            if (scanf("%d", &temp) == 1)
            {
                multArray(dip, SIZE, temp);
                showArray(dip, SIZE);
                break;
            }
            else
            {
                printf("输入有误,请重新输入.\n");
                break;
            }
        default:
            printf("无想要的功能,请重新输入");
            printf("输入希望使用的功能,或者按q退出:\n");
            printf("1. 展示数组当中的数          2. 数组当中的元素乘以一个数在展示.\n");
            break;
        }

        printf("输入希望使用的功能,或者按q退出:\n");
        printf("1. 展示数组当中的数          2. 数组当中的元素乘以一个数在展示.\n");
    }
    
    return 0;
}

void showArray(const double ar[], int n)
{
    int i;

    for (i = 0; i < n; i++)
        printf("%8.3f.\n", ar[i]);
}

void multArray(double ar[], int n, double mult)
{
    int i;

    for (i = 0; i < n; i++)
        ar[i] *= mult;
}

注意:

  1. const可以用来声明指针、数组
  2. const声明的指针不可以修改指向的值(注意,这里是被指向的数据地址锁死了,并不是指针地址锁死了)

示例:

double rates[5] = { 88.88, 77.1, 100.12, 59.45, 183.11 };
const double * p = rates; // pd指向数组的首元素
// 被const声明的数组不允许赋值给未被const声明的指针,因为如果可以就可以通过指针修改只读数组的值了
// cpp当中不允许这样做

不允许修改rates指向的数据的值

可以通过修改rates数组本身达到这个目的

可以修改p的指针地址,例如p++可以被允许

指针和多维数组

关键点:

  • 多维数组的本质和一维数组一致,只是一个指针占的字节大小不同
  • 注意多维数组的+-和维度
  • 注意多维数组的解引用

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/13/2023, 12:04:02 AM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/13/2023, 12:04:02 AM
 * Description: 对多维数组的指针的使用,注意在发生指针加减的时候多维数组的指针的移动
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>

int main(void)
{
    int zip[4][2] = 
    {
        {2, 4},
        {6, 8},
        {1, 3},
        {5, 7}
    };

    printf("zip = %p, zip + 1 = %p.\n", zip, zip + 1); // 这是最外层数组的两个元素之间的距离,是两个int类型元素的字节大小
    printf("zip[0] = %p, zip[0] + 1 = %p.\n", zip[0], zip[0] + 1); // 这是内层数组的两个元素之间的距离,是一个int类型的大小
    printf("*zip = %p, *zip + 1 = %p.\n", *zip, *zip + 1); // 因为是二维数组,所以最外层的解引用得到的结果是指向内层数组的指针
    printf("zip[0][0] = %d.\n", zip[0][0]);
    printf(" *zip[0] = %d.\n", *zip[0]); // 这是解引用了某个地址,所以得到的是具体的数组值zip代表的是指向内层数组的内层数组当中的元素的首元素的指针
    printf("   **zip = %d.\n", **zip); // 双解引用,得到的是内层第一个数组的第一个元素
    printf("    zip[2][1] = %d.\n", zip[2][1]); // 1
    printf("     *(*zip + 2) + 1 = %d.\n", *(*zip + 2) + 1); // 7关键解读
    printf("       **(zip + 1) + 1 = %d.\n", **(zip + 1) + 1); // 这个结果也是7但是指针在外层数组上移动
    /**
     * 最后一个Printf的结果是7
     * 第一层*zip得到的是内层数组的第一个元素的地址,
     * 该地址+2对于内层数组来说已经越界了,但是对于外层数组来说并没有,该地址+2指针会指向外层数组下一层数组的头元素上,
     * 解析该地址得到的是6 ---> 说明这个移动并非是在外层数组上移动,如果是的话那么解析该地址应该得到的还是一个地址
     * 6+1得7
    */
   printf("查看两个解引用的内存地址: *(*zip + 2) = %p, **(zip + 1) = %p.\n", &(*(*zip + 2)), &(**(zip + 1))); // 两者内存地址一样,说明两种移动方式都可以指向该元素

    getchar();

    return 0;
}

指向多维数组的指针:

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/13/2023, 1:08:30 AM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/13/2023, 1:08:30 AM
 * Description: 指向多维数组的指针的声明
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>

int main(void)
{
    int zip[4][2] = 
    {
        {2, 4},
        {6, 8},
        {1, 3},
        {5, 7}
    };
    int (* pz)[2]; // 这是一个指针变量,指向一个内含两个int类型值的数组 -> 指向的是该数组的头指针
    // int * pax[2]; -> 这个声明的是pax是一个数组,里面包含的是两个指针元素,指向int类型的元素的指针
    /**
     * 之所以要用圆括号是因为
     * []运算符优先级要比*要高
    */

    pz = zip;

    printf("pz = %p, pz + 1 = %p.\n", pz, pz + 1); // 相当于外层指针
    printf("pz[0] = %p, pz[0] + 1 = %p.\n", pz[0], pz[0] + 1); // 相当于内层指针
    printf(" *pz = %p, *pz + 1 = %p.\n", *pz, *pz + 1); // 第一个解引用解出来的值也是一个地址,指向内层数组的头指针 -> 和上一个打印结果一致
    printf("pz[0][0] = %d.\n", pz[0][0]);
    printf("  *pz[0] = %d.\n", *pz[0]);
    printf("   **pz = %d.\n", **pz);
    printf("    *(*(pz + 2) + 1) = %d.\n", *(*(pz + 2) + 1)); // 3 -> 通过运算符一层一层去找即可

    getchar();

    return 0;
}

指针的兼容性

关键点:

  • 指向指针的指针要兼容需要被指向的指针指向的值类型相同

示例代码:

/**
 * @Author: Lucifer
 * @Date: 5/14/2023, 9:03:11 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/14/2023, 9:03:11 PM
 * Description: 指针之间的赋值兼容
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>

int main(void)
{
    int n = 5;
    double x;
    int * p;
    double * p2;
    int ** p3; // 指向指针的指针
    double ** p4;

    p = &n;
    p2 = &x;
    p3 = &p;
    p4 = &p2;
    p3 = p4; // 不被允许,因为指向的指针虽然同是地址类型,但是他们指向的值的类型不相同

    x = n; // 可以隐式类型转换
    p = p2; // 不被允许,编译时错误

    return 0;
}

示例代码2:

/**
 * @Author: Lucifer
 * @Date: 5/14/2023, 9:11:06 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/14/2023, 9:11:06 PM
 * Description: 指针的兼容性,二级指针当中通过修改一级指针达到间接修改其一级指针所指向的值
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>

int main(void)
{
    const int ** p2;
    int * p1;
    const int n = 13;

    p2 = &p1; // 允许,但是p2的const限定符会失效
    *p2 = &n; // 在cpp当中就是这样赋值的,const限定符修饰的指针才能被const限定符修饰的变量赋值
    *p1 = 10; // 允许,但是在部分编译器中会导致**p2的值不一致,因为在上诉当中*p2已经指向了n,在这里对一级解引用又进行了修改

    return 0;
}

函数和多维数组

两种情况:

  • 定长数组 -> 用到前面说到的声明二维数组当中的一级指针的方法 (int (* p)[2] or int ar[][2])
  • 变长数组

定长数组:

示例代码:

两个方法,分别计算数组的行之和和列之和

行之和:

int sum_rows(int ar[][COLS], int rows)
{
    int r;
    int c;
    int total;

    for (r = 0; r < rows; r++)
    {
        total = 0;
        for (c = 0; c < COLS; c++)
            total += ar[r][c];
        printf("行 %d 的和是 %d.\n", r, total);
    }

    return total;
}

列之和法1:

int sum_cols(int ar[][COLS], int rows)
{
    int r;
    int c;
    int total;

    for (c = 0; c < COLS; c++) // 修改外层,根据列数求和,列就成了外层
    {
        for (r = 0; r < rows; r++)
            total += ar[r][c];
        printf("列 %d 的和是 %d.\n", c, total);
    }
    
    return total;
}

列之和法2:

int sum_cols_a(int (* p)[COLS], int rows)
{
    /**
     * 注意:int (* p)[COLS]指针其实就是二维数组本身,
     * 并不是二维数组的解引用,
     * 二维数组的解引用是指向一维数组的头元素的指针
    */
    // int * tempP = (* p)[COLS]; // 这个初始化会导致指针指向未知的地址,这个初始化的方式也是有误的
    int r;
    int c;
    int total;

    for (c = 0; c < COLS; c++)
    {
        total = 0;
        for (r = 0; r < rows; r++)
        {
            // 应该在这里初始化指针
            int * tempP = &p[r][c]; // p是一个二维数组,也是一个指向数组的指针
            total += *tempP; // 对指针解引用得值
            tempP++;
        }
    }

    return total;
}

测试的调用函数:

int main(void)
{
    int test[ROWS][COLS] = 
    {
        {2, 4, 6, 8},
        {3, 5, 7, 9},
        {12, 10, 8, 6}
    };

    int sumByRow = sum_rows(test, ROWS);
    int sumByCol = sum_cols(test, ROWS);
    int sumByColP = sum_cols_a(test, ROWS);

    if (sumByCol == sumByColP)
        printf("方法无误!.\n");
    

    printf("行之和: %d.\n", sumByRow);
    printf("列之和: %d.\n", sumByCol);
    printf("列之和2: %d.\n", sumByColP);

    getchar();

    return 0;
}

整体:

/**
 * @Author: Lucifer
 * @Date: 5/14/2023, 9:34:56 PM
 * @LastEditors: Lucifer
 * @LastEditTime: 5/14/2023, 9:34:56 PM
 * Description: 使用定长求二维数组的行和列之和,注意声明一级指针的方式
 * Copyright: Copyright (©)}) 2023 Your Name. All rights reserved.
 */
# include<stdio.h>
# define ROWS 3
# define COLS 4
int sum_rows(int ar[][COLS], int rows);
int sum_cols(int ar[][COLS], int rows);
int sum_cols_a(int (* p)[COLS], int rows);

int main(void)
{
    int test[ROWS][COLS] = 
    {
        {2, 4, 6, 8},
        {3, 5, 7, 9},
        {12, 10, 8, 6}
    };

    int sumByRow = sum_rows(test, ROWS);
    int sumByCol = sum_cols(test, ROWS);
    int sumByColP = sum_cols_a(test, ROWS);

    if (sumByCol == sumByColP)
        printf("方法无误!.\n");

    printf("行之和: %d.\n", sumByRow);
    printf("列之和: %d.\n", sumByCol);
    printf("列之和2: %d.\n", sumByColP);

    getchar();

    return 0;
}

int sum_rows(int ar[][COLS], int rows)
{
    int r;
    int c;
    int total;

    for (r = 0; r < rows; r++)
    {
        total = 0;
        for (c = 0; c < COLS; c++)
            total += ar[r][c];
        printf("行 %d 的和是 %d.\n", r + 1, total);
    }

    return total;
}

int sum_cols(int ar[][COLS], int rows)
{
    int r;
    int c;
    int total;

    for (c = 0; c < COLS; c++) // 修改外层,根据列数求和,列就成了外层
    {
        // 注意初始化total的值,否则将是一个错误的值
        total = 0;
        for (r = 0; r < rows; r++)
            total += ar[r][c];
        printf("列 %d 的和是 %d.\n", c + 1, total);
    }
    
    return total;
}

int sum_cols_a(int (* p)[COLS], int rows)
{
    /**
     * 注意:int (* p)[COLS]指针其实就是二维数组本身,
     * 并不是二维数组的解引用,
     * 二维数组的解引用是指向一维数组的头元素的指针
    */
    // int * tempP = (* p)[COLS]; // 这个初始化会导致指针指向未知的地址,这个初始化的方式也是有误的
    int r;
    int c;
    int total;

    for (c = 0; c < COLS; c++)
    {
        total = 0;
        for (r = 0; r < rows; r++)
        {
            // 应该在这里初始化指针
            int * tempP = &p[r][c]; // p是一个二维数组,也是一个指向数组的指针 -> 第一个形参的参数名是p
            total += *tempP; // 对指针解引用得值
            tempP++;
        }
    }

    return total;
}

注意:

int ar[][COLS], int rows这个形参表明:

  • ar指向一个内含四个int类型值的数组
  • 指向的对象占位16字节
  • ar+1是该地址加上16字节

另一种声明方式:

typedef int arr4[4]; // arr4是一个内含4个int的数组
typedef arr4 arr3x4[3]; // arr3x4是内含3个arr4的数组

上诉方式在头文件当中是合法声明

变长数组

  • 事先不知道数组的长度
  • 实现不知道二维数组的维度

示例代码:

int sumVar(int rows, int cols, int ar[rows][cols]); // 注意,形参的顺序不可以更改,因为第三个参数使用的是前两个参数
int sumVar(int rows, int cols, int ar[rows][cols])
{
    int r;
    int c;
    int total = 0;

    for ( r = 0; r < rows; r++)
        for ( c = 0; c < cols; c++)
            total += ar[r][c];
    
    return total;
}

复合字面量

作用:

使得变量的声明变简单

格式:

(type){value1, value2, ..., valueN}

type表示复合字面量的类型,value1, value2, ..., valueN表示复合字面量中的值

示例代码:

/** 使用复合字面量声明可以变成 */
int main(void)
{
    struct Person p = (struct Person) {"Jun", 23, "Main"};

    int total1, total2, total3;
    int * p1;
    int (* p2)[COLS];
    // 声明复合字面量
    p1 = (int[2]) {10, 20};
    p2 = (int[2][COLS]) { {1, 2, 3, -9}, {4, 5, 6, -8}}; // 这个地址指向的临时数组对象在当前语句或表达式执行完毕后就会被销毁

    total1 = sum(p1, 2);
    total2 = sumByA(p2, 2);
    total3 = sum((int[]) {4, 4, 4, 5, 5, 5}, 6);

    printf("total1 %d.\n", total1);
    printf("total2 %d.\n", total2);
    printf("total3 %d.\n", total3);

    return 0;
}

int sumByA(const int ar[][COLS], int rows)
{
    int r;
    int c;
    int total = 0;

    for ( r = 0; r < rows; r++)
        for ( c = 0; c < COLS; c++)
            total += ar[r][c];
    
    return total;
}

int sum(const int ar[], int n)
{
    int i;
    int total = 0;
    
    for ( i = 0; i < n; i++)
        total += ar[i];

    return 0;
}

标签:int,2023,数组,printf,total,指针
From: https://www.cnblogs.com/JunkingBoy/p/17431696.html

相关文章

  • Go语言中的数组以及其相关特性
    在Go语言中,数组是一种固定长度、相同类型元素的序列。可以将数组视为一个盒子,其中每个元素都有自己的位置(索引)和值。数组的长度是在声明时指定的,一旦定义后,其长度将是固定的,不能动态改变。数组的类型由元素类型和长度决定,例如,[5]int表示一个包含5个整数元素的数组。要声明和初始......
  • java 定义不固定长度的数组
    在Java中,我们可以使用ArrayList来定义不固定长度的数组,因为ArrayList内部使用了一个动态数组来存储元素。ArrayList<Integer>intList=newArrayList<Integer>();intList.add(1);intList.add(2);intList.add(3);intList.a......
  • java Arrays.fill 扩充数组
    importjava.util.*;publicclassImoocStudent{publicstaticvoidmain(Stringargs[]){intarray[]=newint[6];Arrays.fill(array,100);for(inti=0,n=array.length;i<n;i++){System.out.println(array[i])......
  • java 获取数组,最大值,最小值
    以下实例演示了如何通过Collections类的Collections.max()和Collections.min()方法来查找数组中的最大和最小值:importjava.util.Arrays;importjava.util.Collections;publicclassImoocStudent{publicstaticvoidmain(Stringargs[]){Integer[]......
  • java arrays arraycopy 复制数组
    publicstaticvoidmain(Stringargs[]){int[]source={1,2,3,4,5,6,7};int[]target=newint[5];System.arraycopy(source,0,target,0,5);//6,7超出5的长度,被省略了System.out.println(Arrays.toString(target));for(......
  • 字符串原地修改双指针经典实现
    字符串原地修改经常遇到的一类题,双指针一个用于写入,一个用于扫描,互不干扰,各司其职。题目:https://leetcode.cn/problems/reverse-words-in-a-string/stringreverseWords(strings){reverse(s.begin(),s.end());intwrite=0,scan=0;while......
  • java数组添加元素
    importjava.util.ArrayList;importjava.util.Vector;importjava.util.Arrays;publicclassImoocStudent{publicstaticvoidmain(Stringargs[]){intarray[]={2,5,4,-2,-3,-29,20};Arrays.sort(array);printArr("数组排序的结果......
  • 剑指 Offer 56 - II. 数组中数字出现的次数 II
    题目描述:在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。   int[]counts=newint[32];for(inti=0;i<nums.length;i++){for(intj=0;j<32;j++){counts[j]+=nums[i]&1;//更新......
  • C++中const和constexpr关键字解析:常量、函数和指针
    C++中const和constexpr的作用很多C++的初学者看到const这个关键字的第一反应都是一头雾水,主要是因为const可以出现在很多的位置,以及后面加入的constexpr更是常常感到困惑,今天就为大家一一解释出现它们的含义和以及作用const关键字const修饰变量这是最基本的一种用法,顾名思义,就是......
  • js 中数组转树 递归方法
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metahttp-equiv="X-UA-Compatible"content="IE=edge"/><metaname="viewport"content="w......