C语言:数组

这是很基础的教程,我只是写给自己看,作为一个学习笔记记录一下,如果正在阅读的你觉得简单,请不要批评,可以关掉选择离开

如何学好一门编程语言

  • 掌握基础知识,为将来进一步学习打下良好的基础。
  • 上机实践,通过大量的例题学习怎么设计算法,培养解题思路。
  • 养成良好的编码习惯,注释一定要写,要不然保你一周后自己写的代码都不认识了

数组

数组由数据类型相同的一系列元素组成。声明数组需要指定 元素类型 和元素个数。

float candy[365];      /* 内含365个float类型元素的 数组 */
char code[12];         /* 内含12个char类型元素的 数组 */
int states[50];        /* 内含50个int类型元素的 数组 */

所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素candy[0],最高的地址对应最后一个元素candy[365]。

数组初始化

  数组初始化时 一般用 { } 将元素包括,用逗号分隔。元素数量 不能大于 数组声明时在方括号 [ ] 中指定的元素数目。

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

1、如果我们声明的时候 没有指明元素个数,那么数组的大小则为初始化时元素的个数。

double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

2、数组要么在声明的时候全部赋值,要么只能单个赋值

int a[5]={1,1,1,1,1};    // 在声明的时候赋值
// 或者在声明后逐个赋值,有点像追加
int a[0]=1;
int a[1]=1;
int a[2]=1;
int a[3]=1;
int a[4]=1;

int str[5];
str={1,2,3,4,5};        // 错误演示,声明后只能逐个赋值

3、未初始化的数组,编译器使用的值是内存相应位置上的现有值,数据是乱的。

float candy[5];
//-0.005860
//0.000000
//-0.005860
//0.000000
//-58450.625000

4、在使用数组前,最好先将数组初始化,内存置零。养成良好习惯,避免不必要的错误。

char str1[10]={0};    // 在数组声明时,初始化为0
// 或者
char str[10];        // 数组声明
memset(str,'\0', sizeof(str));        // 内存位置置零

5、当初始化列表中的值少于数组元素个数时,编译器会把剩余的元素都初始化为0。

float candy[4]={1.f,2.f};    // 只初始化2项
//1.000000
//2.000000
//0.000000
//0.000000

索引数组:对于数组  int candy[4]; ,索引值只能在0~3以内。不在索引边界虽然不会报错,但是会出现错误的值。

多维数组

多维数组声明的一般形式为:

type name[size1][size2]...[sizeN];
int threedim[5][4];    // 创建一个二维的数组,shape=(5,4)

因此,数组中的每个元素是使用形式为 a[ i , j ] 的元素名称来标识

初始化

// 逐行初始化
int a[3][4] = {  
 {0, 1, 2, 3} ,   /*  初始化索引号为 0 的行 */
 {4, 5, 6, 7} ,   /*  初始化索引号为 1 的行 */
 {8, 9, 10, 11}   /*  初始化索引号为 2 的行 */
};
// 连续初始化
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

多维数组还可以局部赋值,未赋值的元素会自动取0值。

下标索引

int val = a[2][3];    // 获取数组中第 3 行第 4 个元素

使用嵌套循环来处理二维数组:

#include <stdio.h>
 
int main ()
{
   /* 一个带有 5 行 2 列的数组 */
   int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
   int i, j;
 
   /* 输出数组中每个元素的值 */
   for ( i = 0; i < 5; i++ )
   {
      for ( j = 0; j < 2; j++ )
      {
         printf("a[%d][%d] = %d\n", i,j, a[i][j] );
      }
   }
   return 0;
}
a[0][0] = 0
a[0][1] = 0
a[1][0] = 1
a[1][1] = 2
a[2][0] = 2
a[2][1] = 4
a[3][0] = 3
a[3][1] = 6
a[4][0] = 4
a[4][1] = 8
运行结果

指针索引

int zippo[4][2];

  对于二维数组,数组名 数组首元素的数组,其中 首元素包含两个值。

zippo 和&zippo[0]指向的地址一样,但zippo[0]本身是一个内含两个整数的数组。zippo+1指向的是下一行的地址,zippo[0]+1指向的是第0行的下一个元素。

int zippo[4][2] = {{2, 4},
                   {6, 8},
                   {1, 3},
                   {5, 7}};

printf("   zippo = %p,    zippo + 1 = %p\n", zippo, zippo + 1);
// zippo = 0x7fffd15143c0,    zippo + 1 = 0x7fffd15143c8
printf("zippo[0] = %p, zippo[0] + 1 = %p\n", zippo[0], zippo[0] + 1);
// zippo[0] = 0x7fffd15143c0, zippo[0] + 1 = 0x7fffd15143c4
printf("  *zippo = %p,   *zippo + 1 = %p\n", *zippo, *zippo + 1);
// *zippo = 0x7fffd15143c0,   *zippo + 1 = 0x7fffd15143c4
printf("zippo[0][0] = %d\n", zippo[0][0]);     // zippo[0][0] = 2
printf("  *zippo[0] = %d\n", *zippo[0]);       // *zippo[0] = 2
printf("    **zippo = %d\n", **zippo);         // **zippo = 2
printf("      zippo[2][1] = %d\n", zippo[2][1]);   // zippo[2][1] = 3
printf("*(*(zippo+2) + 1) = %d\n", *(*(zippo + 2) + 1));       // *(*(zippo+2) + 1) = 3

索引二维数组的值需要 使用两个间接运算符(*)或者使用两对方括号([])都能获得该值(还可以使用一个*和一对[],但是我们暂不讨论这么多情况)。所以上例中 zippo[2][1]等价的指针表示法是*(*(zippo+2) + 1)。

[]的优先级高于*

字符串

  在C语言中,字符串实际上就是一维的字符数组,只不过字符串 结尾都以null字符 \0 终止。因此字符串的长度 总是比字符数组多1字节。

注意:字符数组和字符串并不是一回事,字符串必须以'\0'结尾,但字符数组是字符组成的数组。所以用printf("%s")的时候,只能打印'\0'结尾的字符数组。

字符数组初始化的方法:

// 字符数组
char site[7] = { 'R', 'U', 'N', 'O', 'O', 'B', '\0' };
// 字符串
char site[] = "RUNOOB";    // 更常用
char site[] = {"RUNOOB"};
char *str="IloveChina";        // 用字符指针指向一个字符串

注意:上述这种字符数组的整体赋值只能在字符数组初始化时使用,不能用于字符数组的赋值,字符数组的赋值只能对其元素一一赋值。

下面的赋值方法是错误的:

char str[20];
str="Iamhappy";

只能对字符数组的各个元素逐个赋值,或者strcpy (官方并不推荐):

char str[10]={'I','','a','m','','h','a','p','p','y'};
strcpy(str, "Iamhappy");

字符串名(数组名)既是指向数组第一个元素的地址,也代表整个数组的索引

字符串相关函数

  • strcpy(s1, s2):复制字符串 s2 到字符串 s1。
  • strcat(s1, s2):连接字符串 s2 到字符串 s1 的末尾。
  • strlen(s1):计算字符串 s1 的长度。不会计算'\0'
  • strcmp(s1, s2):如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
  • strchr(s1, ch):返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
  • strstr(s1, s2):返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
  • put(字符数组名):把字符数组中的字符串输出到显示器。
  • gets  (字符数组名):从键盘上输入一个字符串。
#include<stdio.h>
#include<string.h>

int main() {
    char str1[14] = "runoob";
    char str2[14] = "google";
    char str3[14];
    int len;

    /* 复制 str1 到 str3 */
    strcpy(str3,str1);
    printf("字符串复制:%s\n", str3);        // runoob

    /* 连接 str1 和 str2 */
    strcat(str1, str2);
    printf("字符串拼接:%s\n", str1);    // runoobgoogle

    /* 连接后,str1 的总长度 */
    len = strlen(str1);
    printf("字符串的长度:%d\n", len);    // 12

    return 0;
}

数组与函数

声明数组形参

int *ar形式和int ar[]形式都表示ar是一个指向int的指针。但是,int ar[]只能用于声明形参,空括号代表ar是一个指针。

int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);

当数组是二维数组时如下,

int sum( int (* pt)[4] );
int sum( int pt[][4]);
int sum( int [][4]);

传递数组给函数

当数组作为函数形参时,不能省略参数名,我们可以采用以下三种方式:

方式1:形参是一个 指针

void myFunction(int *param)
    {...}

方式2:形参是一个已定义大小的数组:

void myFunction(int param[10])
    {...}

方式3:形参是一个未定义大小的数组:

void myFunction(int param[])
    {...}

实例:给函数传递数组

#include<stdio.h>

// 函数声明时,参数名称并不重要,参数类型是必须的
double getAverage(int arr[], int size);

// 主函数
int main(void) {
  int balance[5] = {1000, 2, 3, 17, 50};    // 数组初始化
  double avg;        // 变量声明

  /* 传递一个指向数组的指针作为参数 */
  avg = getAverage(balance, 5);    // 函数调用
  printf("平均值是:%f", avg);
  return 0;
}

// 函数定义
double getAverage(int arr[], int size) {
  // 变量声明
  int i;
  double avg;
  double sum = 0;

  for (i = 0; i < size; ++i) {
    sum = sum + arr[i];
  }

  avg = sum / size;
  return avg;
}
平均值是: 214.400000
结果

从函数返回数组

C 语言不允许从函数 返回一个完整的数组。只能 返回指向数组的指针

int * myFunction()
    {...}

例子:生成 10 个随机数,并使用数组来返回它们

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* 定义 要生成和返回随机数的函数 */
int* getRandom(){
    // C 不支持在函数外返回局部变量的地址,
    // 除非定义局部变量为 static 变量。
    static int  r[10];
    int i;

    srand((unsigned)time(NULL)); // 设置随机数种子
    for (i = 0; i < 10; ++i){
        r[i] = rand();
        printf("r[%d] = %d\n", i, r[i]);
    }
    // getRandom是一个返回指针的函数,那么r就是一个指针,指针就是地址,r就是地址
    return r;   
}

int main(){
    int* p; // 声明一个指向整数的指针
    int i;

    p = getRandom();
    for (i = 0; i < 10; i++){
        printf("*(p + %d) : %d\n", i, *(p + i));
    }

    return 0;
}
r[0] = 6742
r[1] = 3486
r[2] = 11181
r[3] = 9850
r[4] = 16663
r[5] = 29286
r[6] = 13426
r[7] = 8210
r[8] = 27862
r[9] = 10451
*(p + 0) : 6742
*(p + 1) : 3486
*(p + 2) : 11181
*(p + 3) : 9850
*(p + 4) : 16663
*(p + 5) : 29286
*(p + 6) : 13426
*(p + 7) : 8210
*(p + 8) : 27862
*(p + 9) : 10451
View Code

数组与指针

  指针的值是它所指向对象的地址,数组名是一个指向数组第一个元素的指针,例:

flizny == &flizny[0]; // 数组名是该数组首元素的地址

flizny 和 &flizny[0] 都是数组首元素的地址(&是地址运算符)。

在指针前面使用 * 运算符可以得到该指针所指向对象的值

dates + 2 == &dates[2]      // 相同的地址
*(dates + 2) == dates[2]    // 相同的值

指针加1,指针的值递增它所指向类型的大小(以字节为单位)

#include <stdio.h>

#define SIZE 2

int main(void) {
  short dates[SIZE] = {1, 2};  // short 是2字节
  short *p;
  short index;
  p = dates;    // 把数组地址赋给指针
  for (index = 0; index < SIZE; index++)
    printf("指针 + %d: %10p, %d\n", index, p + index, *(p + index));   // 指针加1,地址+2, 但是取的值是下一个
  // 指针 + 0: 0x7fffe206c274, 1
  // 指针 + 1: 0x7fffe206c276, 2
  return 0;
}

顺带一提,不要混淆 *(dates+2)和*dates+2。间接运算符(*)的优先级高于+,所以*dates+2相当于(*dates)+2:

*(dates + 2)  // dates第3个元素的值
*dates + 2    // dates第1个元素的值加2

 

参考

菜鸟教程

 

posted @ 2021-07-13 21:18  凌逆战  阅读(155)  评论(0)    收藏  举报