万老师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