【C language part 4】阵列与字串&函式

阵列

  • 阵列是一群具有相同名称或资料型态的变数集合。

  • 由於整个阵列中的变数均具有相同的名称,因此若要存取阵列中的变数,我们只需要透过阵列的 index 来指定就可以了。

  • 阵列与变数的功能都是用来储存资料,但有所不同的是每一个变数只能储存一项资料,而阵列则是由一连串的主记忆体空间组合而成,所以可以同时连续储放多项资料,亦即一次可以宣告多个变数,而不用一个一个宣告。因此,可以少写许多行程序,并且增加程序的可读性。

  • 阵列定义:

    1. 阵列里的每一个元素都必须是同一种资料型态。
    2. 阵列大小须为常数(constant value)
    3. 占用了连续(contiguous)的记忆体位址。
    4. 如果我们不会分配任何初始值给阵列,最好在宣告的时候将阵列初始化为零或 null。
    5. 插入或删除元素时较麻烦,因为需挪移其他元素。
    6. 用来表示有序串列之一种方式。

阵列的应用

让电脑随机给定四十人的分数,并在萤幕显示出来。

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

int main(){
    int i;
    int score[40];
    
    srand( time(NULL) );
    
    for(i = 0; i < 40; i++){
        score[i] = rand(() % 41) + 60; // 把分数设定在 100~60 之间
    }
    
    printf("座号\t分数\n");
    for(i = 0; i < 40; i++){
        printf("%d\t%d\n", (i+1), score[i]);
    }
}

阵列格式

  • 根据阵列结构不同,我们可以把阵列分为
    1. 一维阵列
    2. 二维阵列
    3. 多维阵列
  • 而表示方法如下:
dataType arrayName[arraySize];
dataType arrayName[arraySize] [arraySize];
int score[40]; // 没有宣告初值的阵列
int arr[5] = {3, 4, 5, 6, 7}; // 也可以一并宣告初值
int arr[] = {3, 4, 5, 6, 7}; // 或是用这种方式让电脑自动决定阵列长度
int arr[5] = {0}; // 初始化阵列,所有元素都设为0

但是要切记,阵列的长度要在编译期间就决定好,也就是阵列长度须为常数。如果想要在执行期间,动态生成阵列,要用动态配置记忆体的方式,这边我们等到指标的单元会再提。

int arr[5] = {3, 4, 5, 6, 7};

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

int main(){
    int arr[5] = {0, 10, 100, 1000, 5000};
    
    printf("arr[3] = %d\n\n", arr[3]); // arr[3] = 1000
}

阵列的空间分配方式

无论是几维的阵列,C语言都会分配一块连续的记忆体空间来处理。(这个概念到未来的指标单元会用到)

int x[10];
分配 10*sizeof(int) 个 bytes

int x[5][10];
分配 5*10*sizeof(int) 个 bytes

但如果是呼叫函式传递参数时,如:

void fun(int x[]){

}

这里的阵列 x 就没有分配空间,这样相当於 int *x
这是因为 C语言在呼叫函式传递参数时,无法传递整个阵列,因为阵列可能大的不得了,所以我们传递的是阵列的开头地址,也就是指标。
因此,在参数宣告时,指标和没有宣告大小的阵列可以混用。

范例一 整数阵列

  1. 定义一个阵列空间为 10 的整数一维阵列,以乱数填入介於 ab (a < b)之间的整数元素。
    ab 为使用者输入的整数。
    请列出该阵列的所有元素。
  2. 承上题,计算该整数阵列之平均值(means)。
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

void main(){

    int arr[10] = {0};
    int a, b;

    srand(time(NULL));

    printf("Please enter two number a and b (a < b):\n");
    scanf("%d %d", &a, &b);

    for(int i = 0; i < 10; i++){
        arr[i] =  rand() % (b - a + 1) + a;
    }

    for(int i = 0; i < 10; i++){
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    /* 第二小题 */
    int sum = 0;
    float ave = 0;
    
    for(int i = 0; i < 10; i++){
        sum += arr[i];
    }
    
    ave = sum / 10.0;
    print("\nmean of the array: %f", ave);
}
Please enter two number a and b (a < b):
3 25
arr[0] = 25
arr[1] = 6
arr[2] = 4
arr[3] = 12
arr[4] = 18
arr[5] = 5
arr[6] = 3
arr[7] = 9
arr[8] = 11
arr[9] = 21

mean of the array: 11.4

多维阵列

一维阵列使用阵列名称与一个 index 来指定存取的阵列元素。
二维阵列使用阵列名称与两个 indices 来指定存取的阵列元素,宣告方式与一维阵列类似。

范例二-泡沫排序法 Bubble Sort

我们先来讲解排序演算法(sorting algorithm),但在这之前,我们又要先来再讲讲何谓演算法?

何谓演算法?

  • Wiki的定义:
    为任何良定义的具体计算步骤的一个序列,常用於计算、资料处理和自动推理。精确而言,演算法是一个表示爲有限长列表的有效方法。演算法应包含清晰定义的指令用於计算函式。
  • 简单讲成一句话:可以解决某些问题的有效方法之有限集合。
  • 那麽,在程序的领域,我们再把它缩成一个公式:
    $$
    程序设计 = 资料结构 + 演算法 \
    \text{Programming} = \text{Data Structures} + \text{Algorithm}
    $$
  • 说穿了,演算法就是一种 解决问题的逻辑思维!

排序演算法

所谓排序法,就是将一堆没有排序过的数字由小到大(或大到小)排列好的演算法。
视觉化15种排序法

其中一种程序入门者最常使用的排序演算法-泡沫演算法 Bubble sort

泡沫演算法 Bubble Sort

首先比较最前面两个数字
如果要排序由小到大,那麽将较大的往後排
反之,将较小的数字往後排

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个演算法的名字由来是因为越小的元素会经由交换慢慢「浮」到数列的顶端。

字串 String

  • C语言其实并没有「string」这种类型的资料型态,但我们通常不会只用「一个字元」啊,一般来说都会使用「字串」。
  • 这种时候我们就只能透过字元阵列 char[] 来模拟字串。
  • 要注意:字串一定是 char[] 字元阵列;但是 char[] 字元阵列不一定就是字串。
  • 在 C 谈到字串的话,一个意义是指字元组成的阵列,最後加上一个空(null)字元 '\0',例如底下这个 “hello” 字串:char text[] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
  • 之後可以直接使用以下 function 显示在萤幕上:printf(“%s”, text);
  • 也可以使用 " " 来包含文字,例如:char text[] = “hello”;
  • 虽然在这个例子没有指定空字元 '\0',但是此语法下会自动加上空字元。

范例

我们改成使用 gets() 来读取字串。

所以这里我们可以发现,已经解决刚刚的问题了。
gets() 只有 Enter 键(回车符)才会结束,在 Enter 键以前都会当成字元(包含空白键)。

gets() vs. scanf()

  • gets(str) 允许输入的字符含有空格。
  • scanf("%s", str) 不允许输入字符串含有空格。

注意:
当你编译包含 gets 的程序时,可能会出现 warning 説 gets 是不安全的,原因是 gets( )scanf( )都无法知道字串 s 大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字元阵列 overflow 的情况。
意思就是说,当你输入的字串数量超过接收字串的阵列大小,就很容易发生 overflow。

函式 Function

  • 到目前为止都只使用一个函式(Function),也就是 main() 主函式,若某些程序码经常使用,我们就可以抽出来让它成为一个新函式好让我们重覆呼叫。这也就是所谓的模组化。
  • 什麽叫模组化?简单来说,就是把特定功能分出来当成一个模组,需要的时候只要呼叫这个模组就好;而需要修正的时候也只要修正模组即可。
  • 函式用来将程序组织为一个小的、独立的运行单元,一个函式可以接受资料(传入参数),并执行其中的算法,最後将结果传回去。

函式的优点

  1. 减少撰写重覆的程序码。
  2. 将程序码以有意义的方式组织起来。
  3. 在相同的流程下,可藉由参数调整程序的行为。
  4. 藉由函式库可组织和分享程序码。
  5. 做为资料结构 (data structures) 和物件 (objects) 的基础。

我们就举几个例子来简单示范如何宣告并且使用函数。

范例

没有输入参数,也没有回传值。

主程序

副程序内容

范例

有输入参数,也有回传值。

主程序 简化了前一页的程序方便讲解

副程序内容


通常我们的副函式会写在整个 program 的最前面(main function 前面),不过也可以写在 main function 的後面,但是一开始要先宣告副函式。


<<:  Day7 - 使用 Heroku 建立一个网站

>>:  爬虫怎麽爬 从零开始的爬虫自学 DAY8 python变数使用

【Day1】Infra管理有哪些?

infra 指的是「infrastructure」,也就是IT的基础建设。 那麽IT的基础建设有哪些...

Python & Celery 学习笔记_删除任务

这篇文章主要是在记录,celery 的任务状态以及该如何删除在任务伫列中的任务 有问题欢迎留言讨论喔...

DNS 安全扩展 (DNSSEC)

-DNSSEC 资源记录(来源:InfoBlox) DNSSEC使用数字签名确保DNS 数据的完整...

Unity自主学习(二十):物件脚本(2)

今天既昨天之後,接着继续摸索脚本的编写吧! 打开脚本之後,如果觉得字太小可以按住"Ctrl...

D7- 用 Swift 和公开资讯,打造投资理财的 Apps { 台股申购分析资料来源 }

台股申购资讯 https://www.twse.com.tw/zh/page/announcemen...