万老师C语言笔记:指针与数组

一、数组

1.数组概述

简单数据类型的变量仅能描述一个单独的数据,对客观对象的描述能力十分有限,数组属于构造类型,是相同数据类型数据的集合,元素是组成数组的数据,可以是各种类型。

数组特点

其所有元素数目固定
其所有元素类型相同
其所有元素顺序存放 (在内存中也是连续存放的)

数组作用

集中管理
将相关的同类型数据集中用一个标识符(数组名)表示
元素顺序存放,但可随机定位 用若干个数字序号(下标)来区别各数组元素
例如定义float score[30],可表述30位学生成绩 n 用数组具有什么好处?

2.一维数组的声明

static int y[10];
数组y中的每一个成员都是静态整型成员
extern double s[2];
作了一个外部双精度型数组的引用性声明
应该在另外的源文件中通过double s[2];来定义s 数组,这样第2个声明语句才有意义

3.一维数组的初始化

(1)显式初始化值的个数与说明长度相同
int x[5]={0,1,2,3,4};
int y[5]={0,1,2,3,4,5}; 错误:初值个数大于数组长度
(2)有初始化值时,长度说明可缺省 ,数组长度由初值个数确定
int y[]={1,2,3,4,5,6,7,8};
(3)初始化值的个数可以小于说明长度,但只能缺省最后 连续元素的初值
int z[10]={0,1,2,3,4}; /前5个下标变量赋值/
int u[9]={ , 1, , ,2}; 错误:缺省u[0], u[2]不是最后连续 元素

10.逆波兰表达式

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>
    #include<string.h>
    #define MAX_STACK_SIZE 100

    // 定义全局变量和函数原型
    int stack[MAX_STACK_SIZE];
    int top = -1;

    void push(int value) {
    // 将值压入栈
    if (top < MAX_STACK_SIZE - 1) {
        stack[++top] = value;
    }
    else {
        printf("Stack overflow\n");
        exit(EXIT_FAILURE);
    }
    }

    int pop() {
    // 从栈中弹出值
    if (top >= 0) {
        return stack[top--];
    }
    else {
        printf("Stack underflow\n");
        exit(EXIT_FAILURE);
    }
    }

    int isOperator(char ch) {
    // 检查字符是否是运算符
    return (ch == '+' || ch == '-' || ch == '*' || ch == '/');
    }

    int evaluateRPN(char* expression) {
    char* ptr = expression;
    int operand;
    while (*ptr != '\0') {
    if (isdigit(*ptr)) {
            // 如果是数字,将其转换为整数并压入栈
            operand = 0;
            while (isdigit(*ptr)) {
                operand = operand * 10 + (*ptr - '0');
                ptr++;
            }
            push(operand);
        }
    else if(isOperator(*ptr)) {
            // 如果是运算符,弹出栈顶的两个操作数,进行运算,将结果压入栈
            int operand2 = pop();
            int operand1 = pop();

            switch (*ptr) {
            case '+':
                push(operand1 + operand2);
                break;
            case '-':
                push(operand1 - operand2);
                break;
            case '*':
                push(operand1 * operand2);
                break;
            case '/':
                push(operand1 / operand2);
                break;
            }
            ptr++;
        }
        else {
            // 忽略空格等非数字和运算符的字符
            ptr++;
    }
    }

    // 栈中的最后一个元素即为计算结果
    return pop();
    }
    int main() {
    char expression[MAX_STACK_SIZE];
    gets_s(expression, MAX_STACK_SIZE);// 逆波兰表达式
    int result = evaluateRPN(expression);

    // 输出计算结果
    printf("Result: %d\n", result);

    return 0;
    }

二、10个声明语句辨析:

1.int a;

声明一个整数类型的变量 a

2.int a[10]

声明一个包含10个整数元素的数组 a

3.int f()

声明一个返回值类型为整数,无参数的函数

4.Int *p

声明一个指向整数的指针

5.int *p[10]

声明一个包含10个指向整数的指针的数组 p

6.int *p()

声明一个函数 p,该函数返回一个指向整数的指针

7.int **P

声明一个指向指向整数的指针的指针 P

8.int (*p)[10]

声明一个指向包含10个整数元素的数组的指针 p

9.int (*p)()

声明一个指向返回整数的函数的指针 p

10.int (*p[10])()

声明一个包含10个指向返回整数的函数的指针的数组 p

例1(指针数组,指向数组的指针):

    int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    int *p1[10];      // 数组,每个元素是一个指向 int 的指针
    int (*p2)[10];    // 指针,指向一个包含 10 个 int 元素的数组

    p1[0] = &arr[0];  // 第一个元素指向数组的第一个元素
    p2 = &arr;        // 指针指向整个数组

    // 使用 p1 和 p2 访问数组元素
    printf("%d\n", *p1[0]);   // 输出 arr[0]
    printf("%d\n", (*p2)[0]); // 输出 arr[0]

    return 0;
}

例2(函数指针数组/指向函数的指针)

指向函数的指针的声明: int *p() p=max

    void task0() { printf("task0 is called!\n"); }
    void task1() { printf("task1 is called!\n"); }
    void task2() { printf("task2 is called!\n"); }
    void task3() { printf("task3 is called!\n"); }
    void task4() { printf("task4 is called!\n"); }
    void task5() { printf("task5 is called!\n"); }
    void task6() { printf("task6 is called!\n"); }
    void task7() { printf("task7 is called!\n"); }
    void execute(void (*tasks[])(), char *Task,int num) {
    for (int i = 0; i < num; i++) {
            tasks[Task[i]]();
    }
    }
    void (*function[8])() = { task0, task1, task2, task3, task4, task5, task6,task7 };

例3 复杂声明分析

int main()
{
	int (*   (*   (*  (*q)[2]  )(void)   )  [4]  )(double);
	/*q 是一个指针,指向一个数组,该数组的元素是函数指针,
	每个函数指针接受 void 参数,返回一个指针,指向一个数组,
	该数组的元素是函数指针,每个函数指针接受 double 参数,
	返回一个 int*/
	int(*(*(*p)[3])(void))[4];
	/*p是指针,指向一个数组,数组有两个元素,类型为指针
	指针指向函数,函数无参,返回值是一个指针,指向一个有四个元素
	的整型数组*/
}

方法总结,从里往外剥,如第一个声明:
最内层 (q)[2] 表示 q 是一个指针,指向一个包含两个元素的数组。
中间层 (
(q)[2])(void) 表示这个数组的每个元素是一个函数指针,该函数不接受参数(void),并返回一个指针。
下一层 (
((q)[2])(void))[4] 表示这个指针指向一个包含四个元素的数组,每个元素是一个函数指针,该函数接受 double 类型的参数并返回一个指针。
最外层 int ((((q)[2])(void))[4])(double) 表示这个指针指向的数组的每个元素是一个函数指针,该函数接受 double 类型的参数并返回一个 int。

三、二维数组取值取址辨析

 int a[3][3] = {
 {1, 2, 3},
 {4, 5, 6},
 {7, 8, 9},
 };
 int main()
 {
 printf("a=%x\n", a); //二维数组的起始地址
 printf("*a=%x\n", *a);//第0行的起始地址
 printf("*(a+1)=%x\n", *(a+1));//第1行的起始地址
 printf("*(a+2)=%x\n", *(a + 2));//第2行的起始地址
 printf("*a+1=%x\n", *a+1);//第0行第2个元素地址
 printf("*(a+1) + 1=%x\n", *(a+1) + 1);//第1行第2个元素地址
 printf("*(a + 2) + 1=%x\n", *(a + 2) + 1);//第2行第2个元素地址
 printf("*(*a+1)=%d\n", *(*a + 1));//第0行第2个元素的值
 printf("*(*(a+1) + 1)=%d\n", *(*(a+1) + 1));//第1行第2个元素的值
 printf("*(*(a+1) + 1)=%d\n", *(*(a + 2) + 1));//第2行第2个元素的值
 }

![运行结果示意图](file://C:/Users/muke/Documents/Gridea/post-images/1700461359588.png)

问题1: int*p;p=( )哪个正确?

A  p=a
B  p=*a
C  p=&a[0][0]
答案:BC

问题2: 能否通过p++遍历元素?

答案:可以,在内存中二维数组是顺序存放的,a[0][2]后是a[1][0]
例如: for (int i = 0; i < 9; i++, p++) { printf("%d ", *p); }

问题3:如果想让p++等同于a++,应如何声明

A  int *P
B  int P[4]
C  int (
p)[4]
答案:C
分析:
A 是一个指向整数的指针。
B 是一个包含 4 个指向整数的指针的数组,每个元素是一个指针,记录一个整数元素的地址。
C 是一个指向包含 4 个整数的数组的指针,p++将指向下一个数组。
例如: int* p[3];
p[1] = *(a + 1);
printf("%x\n", *p[1]);
printf("%x\n", p[1]);
printf("%x\n", a + 1);
其中p[1]不能赋值为(a+1),因为(a+1)类型为 int(*)[3],是一个指向包含3个整数的一维数组的指针类型。而 *(a+1)为整数元素的地址,上述程序输出为:
4
5b6ff644
5b6ff644
若改为
printf("%x\n", ++* p[1]);
printf("%x\n", ++p[1]);
printf("%x\n", (a + 2));
则输出
5
d2b0d078
d2b0d080

四、二维数组传参问题

1.传递固定行列的二维数组

我们知道,将一维数组传参给函数时,我们可以写成:
void test(int *a)
void test(int a[])
那二维数组能否传参给下列函数呢?
void test(int **a)
void test(int a[][])

#include<stdio.h>
void test(int **a)
{
	printf(" ");
}

int main()
{
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	test(arr);
	return 0;
}

程序会报错
[Error] cannot convert 'int (*)[4]' to 'int**'
可见:二维数组名其实是一个数组指针,指向的是数组,而int **a是是一个二级指针,指向的是一个指向整数的指针,想到前文谈到的,arr+1实际上移动了一行而不是一个元素,移动的距离取决于列数,如果能够传给二级指针a,那a+1如何知道移动到哪个位置?所以这样传参是错误的,同理int a[][]也是错误的。正确的形式如下:

(1)void test(int a[3][4])

这是显然正确的

(2)void test(int a[][4])

传参时行数是可以省略的,根据前面的分析,列数不能省略

(3)void test(int (*a)[4])

根据报错信息,显然可以改成这样的数组指针(指向数组的指针),因为二维数组a实际上是一维数组的地址&a[0]

(错误)void test(int* a[4])

这里的a是一个指针数组,显然不能接受二维数组arr

2.传递动态二维数组

对于二维数组,一种更通用的方式是使用一维指针来传递,并在函数内部进行适当的解释。

(1)void test(int rows, int cols, int *a)

例如下列函数,

void Func4(int rows, int cols, int *a) 
{
 int i, j;
 for(i = 0; i < rows; ++i) {
     for(j = 0; j < cols; ++j) {
         printf("%d ", a[i * cols + j]);
     }
     printf("\n");
 }
}

另一种方法是利用前面提过的指针数组

(2)void test(int **a)

void test(int **a)
{
	printf("%d %d %d",**a ,*(*a+1) ,*(*(a+1)+1) );
}
int main()
{
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	int* p[3];
	p[0] = arr[0];
	p[1] = arr[1];
	p[2] = arr[2];
	test(p);

	return 0;
}

当行列数不定时,利用for循环将每一行的首地址对应赋值给p即可,p的元素是一个指针,p代表的是首元素的地址,即二级指针,因此能够传递给test(int **a)
想一想,上述程序输出的数是什么?
1   2   6

posted @ 2024-08-23 14:48  Losyi  阅读(36)  评论(0编辑  收藏  举报