一、数组
(一)定义
数组是一组相同数据类型的元素的集合,这些元素在内存中是连续存储的,并且通过一个唯一的数组名称和索引来访问。
(二)数组元素的访问与操作
1.声明时显式初始化在声明数组时,可以显式地为每个元素赋值。
int arr[5] = {1, 2, 3, 4, 5};
2.部分初始化当初始化的元素少于数组大小时,未显式赋值的元素会被默认初始化为0。
int arr[5] = {1, 2};
3.不指定数组大小,由编译器推断可以不指定数组的大小,编译器会根据初始化列表自动推断数组的大小。
int arr[]= {1, 2, 3, 4, 5};
4.使用for循环逐一赋值在数组声明之后,可以使用for循环或其他方式为数组逐一赋值。
int arr[5];
for(int i= 0; i< 5; i++) {
arr[i] = i+ 1;
}
5.静态初始化在声明静态数组时,未显式初始化的元素会自动初始化为0。
static int arr[5];
6.全部初始化为0可以通过只指定大括号而不提供具体值,将数组的所有元素初始化为0。
int arr[5] = {0};
7.访问数组元素通过下标(索引)来访问数组的每个元素,数组下标从0开始。
numbers[0] = 10;//将10赋值给数组的第一个元素
printf("%d", numbers[0]);//输出第一个元素的值
8.数组遍历使用循环遍历数组的所有元素:
for(int i= 0; i< 5; i++) {
printf("%d ", numbers[i]);
}
9.数组的大小和长度:
int length=sizeof(numbers)/sizeof(int)
(三)数组的指针
数组在内存中的存储数组元素在内存中是连续存储的,因此可以通过指针访问相邻元素。假设数组int numbers[5]存储在内存中,其首元素numbers[0]的地址为&numbers[0],那么numbers[1]的地址为&numbers[0] + 1。
数组名可以直接当作指针使用,通过指针访问数组元素:
int numbers[5] = {10, 20, 30, 40, 50};
int *ptr= numbers;
printf("%d", *(ptr+ 1));//输出numbers[1]的值
(四)数组的常见操作
(1)查找数组中的最大值和最小值
遍历数组,找到最大或最小元素:
int max = numbers[0];
for(int i= 1; i< 5; i++) {
if(numbers[i] > max) {
max = numbers[i];
}
}
printf("Max: %d\n", max);
(2)数组元素的求和
使用循环计算数组所有元素的和:
int sum = 0;
for(int i= 0; i< 5; i++) {
sum += numbers[i];
}
printf("Sum: %d\n", sum);
(3)将数组传递给函数
数组作为函数参数时,传递的是数组的首地址,即指针。示例代码:
#include <stdio.h>
void printArray(intarr[], intsize) {
for(int i= 0; i< size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int numbers[5] = {1, 2, 3, 4, 5};//将数组传递给函数
printArray(numbers, 5);
return0;
}
(4)数组越界
访问超过数组大小范围的索引(数组越界)是危险的,可能导致程序崩溃或意外行为。
printf("%d", numbers[10]);//越界访问,行为未定义
二、字符数组
(一)定义
字符数组是用于存储字符序列的数组,每个元素是一个字符类型(char)。字符数组通常用于表示字符串,但本质上它只是字符的集合。
(1)声明语法
char array_name[size];
char name[10];//声明一个长度为10的字符数组
(2)显式初始化
直接在声明时为字符数组赋值。数组name存储字符序列Hello,但不会自动加上字符串结束符\0。
char name[5] = {'H', 'e', 'l', 'l', 'o'};
(3)字符串初始化
使用字符串字面量直接初始化字符数组。自动在末尾添加字符串结束符\0。字符串初始化时数组的大小可以不显式指定,编译器会自动推导。
char greeting[]= "Hello";
用ptintf()函数输出字符串(字符数组)时,遇到'\0'则不再输出,所以推荐使用字符串初始化。或者使用fput()函数输出字符串。
(二)字符数组的常见操作
(1)不可直接赋值
字符串数组不能像基本数据类型那样通过直接赋值的方式进行赋值。例如,下面的代码是错误的:
char str[20];
str = "Hello";//错误,不能直接给字符数组赋值
"Hello"为字面常量,而str为字符数组的首地址,执行到char str[20];时,编译器做了两件事:
-
在内存中划出连续的 20 个字节空间。
-
把str是这 20 个字节的起始地址。编译器没有创建一个名为str的变量来存储这个地址,str只是数组首元素地址的别名,在内存中没有自己的存储空间,所以它是一个常量不能改变。
str = "Hello";是将字面常量"Hello"的地址赋给str。但是数组名str是地址常量,代表了数组首元素的固定内存地址,不能被赋值。
(2)复制字符串进行赋值strcpy(str1,str2)
strcpy(str1,str2)是将str2复制到str1,直到在str2中遇到'\0'停止。使用前可以先判断两个字符长度,以防止会发生溢出。
对于字面量常量"Hello",系统知道"Hello"是一个字符串,会在后面自动加'\0'。
char str[20];
char *p = "Hello";
//用strlen(p)获取实际字符个数(不含 '\0'),+1 是为了给'\0'留位置
if (sizeof(str) > strlen(p) + 1) {
strcpy(str, p);
} else {
printf("目标数组空间不足!\n");
// 可以改用 strncpy(str, p, sizeof(str)-1) 进行截断
}
(3)格式化输出赋值sprintf(str,"%d",n)
char str[20];
int n=123;
sprintf(str,"%d",n);
sprintf(str,"n=%d",n);
(4)实现复制/赋值memcpy(str1,str2,n
memcpy(str1,str2,n):对str1开始往后n个字节复制到str,而str[]中前n个元素为复制的内容,后面其他元素保持不变。
memcpy(str1,str2,strlen(str2)+1):将str2全部复制到str1(包括'\0'),但是用printf()遇到'\0'就结束输出,所以只能输出str1中复制部分,但实际str1剩余元素不变。
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20] = "aaaaaaaa";
char str2[] = "hello";
//strlen(str)计算str实际有效字符个数(不含'\0')
int n = strlen(str2)+1;
//全部复制(包含'\0')
memcpy(str1, str2, n);
//printf()输出时遇到复制的str2的'\0'结束输出
printf("%s\n",str1);
//用 fwrite 输出所有字节到标准输出查看str1实际内容
//输出hello('\0')aa
fwrite(str1, sizeof(char), sizeof(str1), stdout);
printf("\n");
char str[] = "aaaaaaaa";
n=3;
//部分复制(不含'\0'),只从str2地址开始复制3个字节
//输出helloaaa
memcpy(str, str2, n);
//printf()输出时输出全部直到遇到str的'\0'
printf("%s\n",str);
return 0;
}
(5)使用scanf和printf处理字符数组
scnaf()遇到空格会自动忽略空格及其后面的内容。
char name[50];
printf("Enter your name: ");
scanf("%s", name);//读取字符串,自动处理\0
printf("Hello, %s!\n", name);
如果需要输入空格,则需要使用fgets(str,MaxLength,stdin)函数;但是需要注意:输入完成后按回车键同样也会被fgets输出。需要再输入完成之后,手动将回车改成'\0'。
fgets(name, 50, stdin);
printf("Hello, %s!\n", name);
如果需要输入空格但不需要输入回车键,则需要使用scanf("%[^\n]",name);遇到回车停止。
scanf("%[^\n]",name);
printf("Hello, %s!\n", name);
(6)字符数组中的字符串操作
使用标准库函数(如strlen,strcpy,strcat,strcmp)来操作字符数组中的字符串。
#include <stdio.h>
#include <string.h>
int main() {
char first[20] = "Hello";
char second[20] = "World";
strcat(first, second);//字符串拼接
printf("%s\n", first);//输出: HelloWorld
return0;
}
例如:strcmp(str1,str2);比较两个字符串,两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。
设这两个字符串为str1,str2:
- 若str1==str2,则返回零;
- 若str1 > str2,则返回正数;
- 若str1< str2,则返回负数。
(三)字符数组的常见问题
(1)数组大小限制
字符数组必须有足够的空间存储所有字符以及字符串结束符\0。示例错误:
char small[5] = "Hello";//错误:需要6个字符,包括\0
(2)数组越界
字符数组操作时要注意避免越界访问(注意下标范围)。示例错误:
char name[10];
name[10] = 'A';//越界,name[10]不可访问处理
(3)输入中带有空格的字符串
使用fgets()函数来读取包含空格的字符串。示例:
char sentence[100];
fgets(sentence, 100, stdin);//读取一行输入
或者使用scanf("%[^\n]",name);输入空格但不需要输入回车键,遇到回车停止。
scanf("%[^\n]",name);
printf("Hello, %s!\n", name);
三、二维数组
(一)定义
二维数组是相同类型的多行多列数据的集合,类似于一个表格(矩阵)。
- 语法:type array_name[rows][cols];
- 示例:int ar[3][4];——定义了一个3行4列的二维数组。
- 内存布局:二维数组在内存中是线性存储的,存储顺序是按行优先。
- 声明:int ar[3][4];——声明一个3行4列的整数数组,未初始化,内容为随机值。
(二)初始化
(1)显式初始化
int ar[2][3] = {{1, 2, 3}, {4, 5, 6}};
(2)部分初始化
int ar[2][3] = {{1}, {4}};(其余元素初始化为0)
(3)隐式大小推断
int ar[][3] = {{1, 2, 3}, {4, 5, 6}};
注意
当二维数组部分初始化时,未指定的元素默认初始化为0。隐式大小推断时,必须指定列数,行数可以根据初始化内容自动推断。
(三)二维数组的遍历
使用嵌套循环遍历二维数组:外层循环遍历行,内层循环遍历列。示例:
for(int i= 0; i< 3; i++) {
for(int j = 0; j < 4; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
(四)二维数组的拷贝操作
可以使用memcpy函数来拷贝整个二维数组的数据块。示例:
memcpy(arr2, arr1, sizeof(arr1));//将arr1的内容拷贝到arr2
(五)二维数组与指针的关系
二维数组名作为指向首行的指针:arr是指向arr[0]的指针,arr[0]又是指向第一行的指针。
通过指针访问二维数组元素:可以通过指针偏移来访问二维数组中的元素。示例:
int *ptr= &arr[0][0];
printf("%d\n", *(ptr+ 1));//访问arr[0][1]
四、习题练习
(一)选择排序实现数组排序
#include <stdio.h>
#include <string.h>
int main(){
int arr[] = {12, 0, 2, 4, 3, 9, 0, 0};
int min, ptr, i, j,temp;
for (i = 0; i < sizeof(arr) / sizeof(arr[0]) - 1; i++){
//初始化后面元素最小值与其下标
min = arr[i + 1];
ptr = i + 1;
for (j = i + 1; j<sizeof(arr) / sizeof(arr[0]); j++){
//此循环用于找到i后面的元素中的最小值和下标。
if (arr[j] < min)
{
min = arr[j];
ptr = j;
}
}
//将第i个元素与后面元素最小值进行比较
if(min<arr[i]){
//如果大则进行交换
temp=arr[i];
arr[i]=min;
arr[ptr]=temp;
}
}
for(i=0;i<sizeof(arr)/sizeof(arr[0]);i++){
printf("%d ",arr[i]);
}
return 0;
}
(二)数组去重(向前覆盖版)
#include <stdio.h>
int main()
{
int arr[10];
int length = sizeof(arr) / sizeof(arr[0]);
printf("请输入%d个数组元素:", length);
for (int i = 0; i < length; i++){
scanf("%d", &arr[i]);
}
for (int i = 0; i < length; i++){ // 依次遍历数组每个元素i
for (int j = i + 1; j < length; j++){ // 遍历i后面所有元素,找到如第i个元素相等的元素
if (arr[j] == arr[i]){ // 进行覆盖(被覆盖的元素实现删除功能)
// 但是只要向前覆盖,最后面的元素实际未被删除
// 只是最后输出时没有输出而已
for (int k = j; k < length - 1; k++){
arr[k] = arr[k + 1];
}
// 不管是不是最后一个元素,都需要被覆盖或删除,所以--
length--;
// 当前j被j+1覆盖,即当前j对应元素为j+1对应的元素
// 所以需要回退到j-1,继续执行for循环以检查当前j(原来的j+1)对应元素值
j--;
}
}
}
for (int i = 0; i < length; i++){
// 只打印不重复元素
printf("%d ", arr[i]);
}
printf("\n");
// 输出全部元素(包含最后面的覆盖元素)
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){
printf("%d ", arr[i]);
}
return 0;
}
(三)拼接字符串(不使用strcat函数)
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[100],arr2[100];
strcpy(arr1,"hello,");
strcpy(arr2,"123456");
int len1=strlen(arr1);
int len2=strlen(arr2);
int i;
for(i=len1;i<len1+len2;i++){
arr1[i]=arr2[i-len1];
}
arr1[i]='\0';
printf("%s",arr1);
return 0;
}
(四)计算3X3对角线元素之和
#include <stdio.h>
#include <string.h>
int main()
{
int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int sum=0;
for(int i=0;i<3;i++){
sum+=arr[i][i];
}
printf("%d\n",sum);
return 0;
}
(五)实现矩阵转置
#include <stdio.h>
int main()
{
int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int temp;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
for (int i = 0; i < 3; i++)
{
for (int j = i; j < 3; j++)
{
temp = arr[i][j];
arr[i][j] = arr[j][i];
arr[j][i] = temp;
}
}
printf("\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}

3721

被折叠的 条评论
为什么被折叠?



