C语言学习之数组

一、数组

(一)定义

数组是一组相同数据类型的元素的集合,这些元素在内存中是连续存储的,并且通过一个唯一的数组名称和索引来访问。

(二)数组元素的访问与操作

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];时,编译器做了两件事:

  1. 在内存中划出连续的 20 个字节空间。

  2. 把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:

  1. 若str1==str2,则返回零; 
  2. 若str1 > str2,则返回正数; 
  3. 若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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值