5.数组和指针入门

第五讲 数组和指针入门

概述

数组是一组相同类型数据的集合,通过索引访问各个元素。指针是存储内存地址的变量,它提供了直接访问内存的能力。

一维数组

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指向下一个元素(不是下一个字节)