# [C語言] 指標、陣列與結構
相較於`基本型別`int, double,C語言還有`衍伸資料型別(Derived datatype)`。
其中很常被應用的,就是`指標(pointer)`、`陣列(Array)`、`結構(struct)`
## 指標(Pointer)
指標的設計,是為了讓CPU可以**間接取得**資料,有以下優點:
- 可動態配置/釋放記憶體
- 不同的函式可共享大量的儲存空間
- 改善程式存取資料的效率
#### 指標的特性
指標本身是一個物件,指向任何一個**已存在物件**,可以被指定或拷貝
- 指標宣告時,就要定義是指向哪種資料型態的物件
- 指標本身儲存的值,有三種情況
- 指向一個物件,值裡面存放另一個物件的位址
- 指向另一個物件尾端後的位置
- 沒有指向任何物件,值即為null
``` C
int i = 42;
int *p = &i; //p指向i
```
``` C=
#include <stdio.h>
int main() {
int i = 42;
int *p; //* 跟在一個型別,且是宣告的一部份,所以p是一個pointer
p = &i; //p指向i
printf("i: %d\n", i);
printf("*p: %d\n", *p);
printf("p: %p\n", p);
printf("&i: %p\n", &i);
return 0;
}
```
## 陣列(Array)
### 陣列的定義
是一個複合資料型別,放置**單一型別**物件的容器,並且以位置來存取它們,有固定的尺寸。
通常是存放`char`, `int`型別的資料
### 字元陣列的特性
在C語言,一個字串本身會在結尾自帶`\0`結束字元,所以當我們把一個字串用陣列儲存時,實際結尾會有一個`\0`也被儲存。
例如: `char str[20] = "Hello"`
在陣列的存放,是一個長度為6的陣列,最後擺放`\0`
### 宣告與初始化陣列
- 靜態宣告
```c
int a[10]; //宣告一個10個int物件的陣列
int *p[10]; //宣告一個10個int指標的陣列
```
因為陣列的長度要在編譯期就決定好。如果想要在執行期動態生成陣列,要用動態配置記憶體的方式。
- 動態宣告
我們同樣用 malloc() 函式來配置記憶體。參考以下敘述:
```c=
int *arr = (int *) malloc(sz * sizeof(int));
```
我們以 sizeof 求得單一元素的大小後,乘上陣列的長度 sz 即可配置一塊足夠大小的記憶體,用來儲存陣列 arr 的元素。由此可知,陣列在電腦中以是一整塊連續的記憶體來儲存,所以可以用索引值快速存取。
如果想要在配置記憶體時一併將元素初始化為 0,改用 calloc() 函式即可。但 calloc() 函式的參數略有不同:
```
int *arr = (int *) calloc(sz, sizeof(int));
```
由於多了初始化的動作,calloc() 函式會比 malloc() 函式慢一點點。
使用完後同樣要釋放記憶體:
```c=
free(arr);
```
由於陣列內部是單一且連續的記憶體區塊,所以可在單一 free() 函式呼叫中釋放掉。不論使用 malloc() 或 calloc(),皆使用 free() 來釋放記憶體。
我們來看一個動態配置記憶體陣列的範例:
```c=
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int size = 0;
printf("輸入長度:");
scanf("%d", &size);
int *arr = malloc(size * sizeof(int));
printf("指定元素:\n");
for(int i = 0; i < size; i++) {
printf("arr[%d] = ", i);
scanf("%d" , arr + i);
}
printf("顯示元素:\n");
for(int i = 0; i < size; i++) {
printf("arr[%d] = %d\n", i, *(arr+i));
}
free(arr);
return 0;
}
```
### 存取陣列元素
陣列使用零或正整數存取陣列元素。參考以下範例:
```clike=
#include <stdio.h>
int main(void)
{
int arr[] = {3, 4, 5};
printf("%d", arr[0]);
printf("%d", arr[1]);
printf("%d", arr[2]);
return 0;
}
```
### 指標算術
對一個指標做加減,會移動指向陣列中的位置
``` C++
#include <iostream>
using namespace std;
int main() {
int a[] = {1,2,3,4,5};
int *ip = a; //等同於int *ip = &a[0]
int *ip2 = ip + 4; //ip2指向a[4]
cout << ip2 << endl;
return 0;
}
```
### 走訪陣列
走訪陣列元素的方式是使用 for 迴圈搭配計數器 (counter)。參考下例:
```clike=
#include <stdio.h>
int main(void)
{
int arr[] = {3, 4, 5, 6, 7};
for (int i = 0; i < 5; i++) {
printf("%d\n", arr[i]);
}
return 0;
}
```
### 計算陣列大小
C 陣列本身沒有儲存陣列大小的資訊。如果想要知道陣列的大小,得自行計算。參考下例:
```clike=
#include <stdio.h>
int main(void)
{
int arr[] = {3, 4, 5, 6, 7};
int sz = sizeof(arr) / sizeof(int);
printf("%d", sz);
return 0;
}
```
在此範例程式中,我們在第 6 行分別計算陣列大小和陣列元素大小,將其相除後即可得陣列長度。在本例中其值為 5。
但這個方式只對自動配置記憶體的陣列有效,若陣列使用動態配置記憶體,則無法使用這個方法。
### 多維陣列
先前的範例皆為一維陣列,但 C 語言允許多維陣列。參考以下宣告多維陣列的敘述:
```clike=
int mtx[3][2] = {
{1, 2},
{3, 4},
{5, 6}
};
```
我們同樣可以對多維陣列存取索引值:
```clike=
#include <stdio.h>
int main(void)
{
int mtx[3][2] = {
{1, 2},
{3, 4},
{5, 6}
};
printf("%d\n", mtx[1][1]);
return 0;
}
```
## 結構(struct)
### 宣告struct
`結構(struct)`可以讓使用者創造自己定義的資料型別
例如一本書的銷售資訊包含多種資料:
``` C=
struct Sales_data {
char* book_no;
int units_sold;
int price;
double revenue;
};
```
其中`book_no`, `units_sold `, `price`,`revenue`是這個struct的data members,簡稱members
### 呼叫struct
``` c=
#include <stdio.h>
struct Sales_data {
char* book_no;
int units_sold;
int price;
double revenue;
};
int main() {
struct Sales_data book1;
book1.book_no = "AAA";
book1.units_sold = 1000;
book1.price = 200;
book1.revenue = book1.units_sold * book1.price;
printf("Revenue: %.2f\n", book1.revenue);
return 0;
}
```
###### tags: `C/C++程式語言觀念`