第五讲 数组和指针入门
概述
数组是一组相同类型数据的集合,通过索引访问各个元素。指针是存储内存地址的变量,它提供了直接访问内存的能力。
一维数组
1.问题的引入
引例1:固定数量的学生成绩查询
#include <stdio.h>
int student1 = 45;
int student2 = 56;
int student3 = 67;
int num;
int main()
{
printf("请输入学员的学号:");
scanf("%d", &num);
switch(num)
{
case 1: printf("学员1的成绩为: %d", student1); break;
case 2: printf("学员2的成绩为: %d", student2); break;
case 3: printf("学员3的成绩为: %d", student3); break;
default: printf("无此学员"); break;
}
return 0;
}
问题:当学生数量增加时,需要定义大量变量,代码冗长且难以维护。
引例2:顺序输入学生成绩
#include <stdio.h>
int student1, student2, student3;
int num;
int main()
{
printf("请输入学员1的成绩:\n");
scanf("%d", &student1);
printf("请输入学员2的成绩:\n");
scanf("%d", &student2);
printf("请输入学员3的成绩:\n");
scanf("%d", &student3);
printf("请输入学员的学号:");
scanf("%d", &num);
switch(num)
{
case 1: printf("学员1的成绩为: %d", student1); break;
case 2: printf("学员2的成绩为: %d", student2); break;
case 3: printf("学员3的成绩为: %d", student3); break;
default: printf("无此学员"); break;
}
return 0;
}
问题:仍然需要为每个学生定义单独的变量。
2.数组的引入
数组的基本形式
类型 数组名[大小];
特点:
数组中的所有元素必须是相同类型
通过下标(索引)访问元素,下标从0开始
数组名表示数组的首地址
使用数组改进的学生成绩系统
#include <stdio.h>
int student[3]; // 定义可以存储3个int的数组
int i;
int num;
int main()
{
// 输入成绩
for(i = 0; i < 3; i++)
{
printf("请输入学员%d的成绩:", i + 1);
scanf("%d", &student[i]);
}
// 查询成绩
printf("请输入要查询的学员学号(1-3):");
scanf("%d", &num);
if(num >= 1 && num <= 3)
{
printf("学员%d的成绩为: %d\n", num, student[num-1]);
}
else
{
printf("无此学员\n");
}
return 0;
}
3.数组排序实例
冒泡排序算法
#include <stdio.h>
int main()
{
int numbers[10];
int i, j, temp;
// 输入10个数字
printf("请输入10个数字:\n");
for(i = 0; i < 10; i++)
{
scanf("%d", &numbers[i]);
}
// 冒泡排序
for(i = 0; i < 9; i++)
{
// 外层循环控制排序轮数
for(j = 0; j < 9 - i; j++)
{
// 内层循环比较相邻元素
if(numbers[j] > numbers[j+1])
{
// 交换元素
temp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = temp;
}
}
}
// 输出排序后的结果
printf("排序后的数字:\n");
for(i = 0; i < 10; i++)
{
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
算法说明:
每一轮将最大的元素"冒泡"到最后
共需要n-1轮排序(n为数组长度)
每轮比较次数递减
多维数组
1.二维数组的概念
二维数组可以看作是一个表格,有行和列。类似于平面坐标系。
声明和初始化
类型 数组名[行数][列数];
示例:二维数组的使用
#include <stdio.h>
int main()
{
// 声明并初始化一个2行3列的二维数组
int a[2][3] = {
{1, 2, 3},
{5, 6, 9}
};
// 访问元素
printf("a[0][0] = %d\n", a[0][0]); // 输出:1
printf("a[0][1] = %d\n", a[0][1]); // 输出:2
printf("a[1][2] = %d\n", a[1][2]); // 输出:9
// 使用循环遍历二维数组
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 3; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
内存存储:二维数组在内存中是按行连续存储的。
特殊数组
1.常量数组
使用const关键字定义的数组在程序运行过程中不能被修改。
const char hexDigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
// 尝试修改会编译错误
// hexDigits[0] = 'G'; // 错误!
优势:
数据存储在只读区域,节省内存
防止意外修改,提高程序安全性
2.字符数组与字符串
字符和字符串的区别
字符:用单引号括起来的单个字符,如’a’
字符串:用双引号括起来的字符序列,如"hello"
字符串的两种表示方式
// 方式1:字符数组
char str1[] = {'h', 'e', 'l', 'l', 'o', '\0'}; // 手动添加结束符
// 方式2:字符串字面量(推荐)
char str2[] = "hello"; // 自动添加结束符'\0'
重要:字符串总是以\0(空字符)结尾。
字符串的输入输出
#include <stdio.h>
int main()
{
char name[50];
// 输入字符串
printf("请输入你的名字:");
scanf("%s", name);// 注意:name前不需要&,因为数组名就是地址
// 输出字符串
printf("你好,%s!\n", name);
return 0;
}
注意事项:
%s用于字符串,%c用于单个字符
scanf遇到空格会停止,可用gets或fgets读取带空格的字符串
指针基础
1.指针的概念
指针:一个变量,存储的是另一个变量的内存地址。
内存地址:内存中每个字节都有一个唯一的编号。
指针变量的声明和初始化
int a = 10; // 普通变量
int *p = &a; // 指针变量,存储a的地址
指针相关运算符
&:取地址运算符,获取变量的地址
*:解引用运算符,获取指针指向的值
2.指针的基本操作
示例:指针的基本使用
#include <stdio.h>
int main()
{
int a = 50;
int *p = &a; // p指向a
printf("a的值: %d\n", a); // 输出:50
printf("a的地址: %p\n", &a); // 输出a的地址
printf("p的值(即a的地址): %p\n", p); // 输出:与&a相同
printf("p指向的值: %d\n", *p); // 输出:50
// 通过指针修改a的值
*p = 100;
printf("修改后a的值: %d\n", a); // 输出:100
return 0;
}
3.指针作为函数参数
值传递 vs 指针传递
#include <stdio.h>
// 值传递:无法修改原变量
void swapByValue(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
// 指针传递:可以修改原变量
void swapByPointer(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a = 10, b = 20;
printf("交换前:a = %d, b = %d\n", a, b);
// 值传递(无效)
swapByValue(a, b);
printf("值传递后:a = %d, b = %d\n", a, b); // 未改变
// 指针传递(有效)
swapByPointer(&a, &b);
printf("指针传递后:a = %d, b = %d\n", a, b); // 已交换
return 0;
}
4.指针与数组
数组名即首地址
int arr[5]={1,2,3,4,5};
int *p=arr;//数组名arr就是首元素arr[0]的地址
三种访问数组元素的方式
#include <stdio.h>
int main()
{
int arr[3] = {10, 20, 30};
int *p = arr;
// 三种等价的访问方式
printf("%d\n", arr[0]); // 方式1:数组下标
printf("%d\n", *(arr + 0)); // 方式2:指针运算
printf("%d\n", *p); // 方式3:指针解引用
return 0;
}
使用指针遍历数组
#include <stdio.h>
int main()
{
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
int size = sizeof(arr) / sizeof(arr[0]);
// 方式1:使用指针算术
for(int i = 0; i < size; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
// 方式2:移动指针
p = arr;
// 重置指针位置
while(p < arr + size)
{
printf("%d ", *p);
p++; // 指针向后移动一个元素
}
printf("\n");
return 0;
}
5.常见指针错误及防范
错误1:野指针(未初始化的指针)
int *p; // 危险:指针未初始化
*p = 10; // 错误:访问未知内存
防范:总是初始化指针
int *p = NULL; // 安全:初始化为空指针
错误2:空指针解引用
int *p = NULL;
*p = 10; // 错误:不能解引用空指针
防范:使用前检查
if (p != NULL)
{
*p = 10; // 安全
}
错误3:指针类型不匹配
int a = 10;
char *p = &a; // 警告:类型不匹配
6.实用示例:使用指针找最大值
#include <stdio.h>
// 返回指向最大值的指针
int* findMax(int arr[], int size)
{
int *max = &arr[0]; // 假设第一个元素最大
for(int i = 1; i < size; i++)
{
if(arr[i] > *max)
{
max = &arr[i]; // 更新最大值的地址
}
} return max;
}
int main()
{
int numbers[] = {23, 45, 12, 67, 34};
int *maxPtr = findMax(numbers, 5);
printf("数组:");
for(int i = 0; i < 5; i++)
{
printf("%d ", numbers[i]);
}
printf("\n");
printf("最大值: %d\n", *maxPtr);
printf("最大值的地址: %p\n", maxPtr);
return 0;
}
关键要点总结
数组要点
数组定义:类型 数组名[大小]
下标从0开始:arr[0]是第一个元素
连续存储:数组元素在内存中连续存放
边界检查:C语言不自动检查数组越界,需要程序员注意
指针要点
指针存储地址:指针变量存储的是内存地址
&取地址:获取变量的内存地址
*解引用:通过指针访问指向的值
初始化很重要:避免野指针,初始化为NULL
类型匹配:指针类型要与指向的变量类型匹配
数组与指针的关系
数组名即地址:arr等同于&arr[0]
指针可以模拟数组:p[i]等同于*(p + i)
指针运算:p + 1指向下一个元素(不是下一个字节)