常數符號化:
#include <stdio.h>
const int red = 0;
const int yellow =1;
const int green = 2;
int main(int argc, char const *argv[]) {
int color = -1;
char *colorName = NULL;
printf("輸入你喜歡的顏色的代號:");
scanf("%d", &color);
switch (color) {
case red: colorName = "red"; break;
case yellow: colorName = "yellow"; break;
case green: colorName = "green"; break;
default: colorName = "unknown"; break;
}
printf("你喜歡的顏色是%s\n", colorName);
return 0;
}
列舉:
#include <stdio.h>
enum COLOR {RED, YELLOW, GREEN};
int main(int argc, char const *argv[]) {
int color = -1;
char *colorName = NULL;c
printf("輸入你喜歡的顏色的代號:");
scanf("%d", &color);
switch (color) {
case RED: colorName = "red"; break;
case YELLOW: colorName = "yellow"; break;
case GREEN: colorName = "green"; break;
default: colorName = "unknown"; break;
}
printf("你喜歡的顏色是%s\n", colorName);
return 0;
}
列舉:
列舉是一種使用者定義的數據型別,它用關鍵字enum以如下語法來宣告:
enum 列舉型別名字 {名字0, ..., 名字n};
列舉型別名字通常並不真的使用,要用的是在大括號裡的名字,因爲它們就是常數符號,它們的型別是int,值則依次從0到n。如:
enum color {red, yellow, green};
就建立了三個常數,red的值是0,yellow是1,而green是2。
當需要一些可以排列起來的常數值時,定義列舉的意義就是給了這些常數值名字。
#include <stdio.h>
enum color {red, yellow, green};
void f(enum color c);
int main(void) {
enum color t = red;
scanf("%d", &t);
f(t);
return 0;
}
void f(enum color c) {
printf("%d\n", c);
}
套路:自動計數的列舉
#include <stdio.h>
enum COLOR {RED, YELLOW, GREEN, NumCOLORS};
int main(int argc, char const *argv[]) {
int color = -1;
char *ColorNames[NumCOLORS] = {
"red", "yellow", "green",
};
char *colorName = NULL;
printf("輸入你喜歡的顏色的程式碼:");
scanf("%d", &color);
if (color>=0 && color<NumCOLORS) {
colorName = ColorName[color];
} else {
colorName = "unknown";
}
printf("你喜歡的顏色是%s\n", colorName);
return 0;
}
列舉量:
宣告列舉量的時候可以指定值
enum COLOR {RED=1, YELLOW, GREEN=5};
#include <stdio.h>
enum COLOR {RED=1, YELLOW, GREEN=5, NumCOLORS};
int main(int argc, char const *argv[]) {
printf("code of GREEN is %d\n", GREEN);
return 0;
}
列舉只是int:
#include <stdio.h>
enum COLOR {RED=1, YELLOW, GREEN=5, NumCOLORS};
int main(int argc, char const *argv[]) {
enum COLOR color = 0; // 本應該用符號量賦值,如:RED等。但未進行列舉型別轉換,而直接用數位,是因爲編譯器在這方面的放鬆,主要是這個不常用
printf("code of GREEN is %d\n", GREEN);
printf("and color is %d\n", color);
return 0;
}
列舉:
主要作用:定義符號量,而不是當作型別使用。C的列舉不太成功!主要是爲了定義符號量。
宣告結構型別:
#include <stdio.h>
int main(int argc, char const *argv[]) {
struct date {
int month;
int day;
int year;
}; // 注意不要漏了這個分號!
struct date today;
today.month = 07;
today.day = 31;
today.year = 2014;
printf("Today's date is %i-%i-%i.\n", today.year, today.month, today.day);
return 0;
}
在函數內/外?
#include <stdio.h>
struct date {
int month;
int day;
int year;
};
int main(int argc, char const *argv[]) {
struct date today;
today.month = 07;
today.day = 31;
today.year = 2014;
printf("Today's date is %i-%i-%i.\n", today.year, today.month, today.day);
return 0;
}
宣告結構的形式:
struct point {
int x;
int y;
};
struct point p1, p2; // p1和p2都是point裏面有x和y的值
struct {
int x;
int y;
} p1, p2; // p1和p2都是一種無名結構,裏面有x和y
struct point {
int x;
int y;
} p1, p2; // p1和p2都是point裏面有x和y的值
結構的初始化:
#include <stdio.h>
struct date {
int month;
int day;
int year;
};
int main(int argc, char const *argv[]) {
struct date today = {07, 31, 2014};
struct date thismonth = {.month=7, .year=2014}; // 和陣列一樣沒給的值添零
printf("Today's date is %i-%i-%i.\n", today.year, today.month, today.day);
printf("This month is %i-%i-%i.\n", thismonth.year, thismonth.month, thismonth.day);
return 0;
}
結構成員:
結構和陣列有點像
陣列用[]運算子和下標存取其成員
a[0] = 10;
結構用.運算子和名字存取其成員
today.day
student.firstName
p1.x
p1.y
結構運算:
要存取整個結構,直接用結構變數的名字
對於整個結構,可以做賦值、取地址,也可以傳遞給函數參數
p1 = (struct point){5, 10}; // 相當於p1.x = 5; p1.y = 10;
p1 = p2; // 相當於p1.x = p2.x; p1.y = p2.y;
陣列無法做這兩種運算!
結構指針:
和陣列不同,結構變數的名字並不是結構變數的地址,必須使用&運算子
struct date *pDate = &today; // today本身無法列印?類似於int x
結構作爲函數參數:
int numberOfDays(struct date d)
輸入結構:
#include <stdio.h>
struct point {
int x;
int y;
};
void getStruct(struct point);
void output(struct point);
void main() {
struct point y = {0, 0};
getStruct(y);
output(y);
}
void getStruct(struct point p) {
scanf("%d", &p.x);
scanf("%d", &p.y);
printf("%d,%d", p.x, p.y);
}
void output(struct point p) {
printf("%d,%d", p.x, p.y);
}
解決的方案:
struct point inputPoint() {
struct point temp;
scanf("%d", &temp.x);
scanf("%d", &temp.y);
return temp;
}
void main() {
struct point y = {0, 0};
y = inputPoint();
output(y);
}
指向結構的指針:
struct date {
int month;
int day;
int year;
} myday;
struct date *p = &myday;
(*p).month = 12;
p->month = 12;
#include <stdio.h>
struct point {
int x;
int y;
};
struct point* getStruct(struct point*);
void output(struct point);
void print(cosnt struct point *p);
int main(int argc, char const *argv[]) {
struct point y = {0, 0};
getStruct(&y);
output(y);
output(*getStruct(&y));
print(getStruct(&y));
getStruct(&y)->x = 0; // 在賦值的左邊
*getStruct(&y) = (struct point){1, 2};
return 0;
}
struct pint* getStruct(struct point *p) {
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("%d, %d", p->x, p->y);
return p; // 再傳回去一個指針是爲了方便再運算
}
void output(struct point p) {
printf("%d, %d", p.x, p.y);
}
void print(const struct point *p) {
printf("%d, %d", p->x, p->y);
}
結構陣列:
struct date dates[100];
struct date dates[] = {
{4, 5, 2005},
{2, 4, 2005}
};
結構中的結構:
struct dateAndTime {
struct date sdate;
struct time stime;
};
巢狀的結構:
struct point {
int x;
int y;
};
struct rectangle {
struct point pt1;
struct point pt2;
};
// 如果有變數 struct rectangle r;
// 就可以有:r.pt1.x、r.pt1.y,r.pt2.x和r.pt2.y
// 如果有變數定義:struct rectangle r, *rp; rp = &r;
// 那麼下面 下麪的四種形式是等價的:
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
// 但是沒有rp->pt1->x(因爲pt1不是指針)
結構中的結構的陣列:
#include <stdio.h>
struct point {
int x;
int y;
};
struct rectangle {
struct point p1;
struct point p2;
};
void printRect(struct rectangel r) {
printf("<%d,%d> to <%d,%d>\n", r.p1.x, r.p1.y, r.p2.x, r.p2.y);
}
int main(int argc, char const *argv[]) {
int i;
struct rectangle rects[] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; // 2 rectangle
for (i=0; i<2; i++) printRect(rects[i]);
return 0;
}
struct的記憶體大小是成員個數n*成員裡佔據最大空間的成員佔據的空間,而地址是連續的,但是中間會有記憶體縫隙
自定義數據型別:(typedef 方法看最後一個單詞是它的名字)
C語言提供了一個叫做 typedef 的功能來宣告一個已有的數據型別的新名字。比如:
typedef int Length // 使得Length成爲int型別的別名
這樣,Length這個名字就可以代替int出現在變數定義和參數宣告的地方了:
Length a, b, len;
Length numbers[10];
Typedef:宣告新的型別的名字
typedef long int64_t; // 過載已有的型別名字,新名字的含義更清晰,具有可移植性
typedef struct ADate {
int month;
int day;
int year;
} Date; // 簡化了複雜的名字
int64_t i = 100000000000;
Date d = {9, 1, 2005};
聯合:相似於struct
union AnElt {
int i;
char c;
} elt1, elt2;
elt1.i = 4;
elt2.c = 'a';
elt2.i = 0xDEADBEEF;
作用:可以得到一個int、double、flaot等內部的各個位元組。
檔案操作,把一個整數以二進制的形式寫到檔案裡的時候,做讀寫的中間媒介。
#include <stdio.h>
typedef union {
int i;
char ch[sizeof(int)];
} CHI;
int main(int argc, char const *argv[]){
CHI chi;
unsigned int i;
chi.i = 1234;
for (i=0; i<sizeof(int); i++) {
printf("%02hhX", chi.ch[i]);
}
printf("\n");
return 0;
}
__func__
當前函數的名字
全域性變數初始化:
區域性變數不會初始化:
#include <stdio.h>
int f(void);
int gAll = f(); // 指明不是一個編譯時刻的常數,不可如此寫
int main(int argc, char const *argv[]) {
printf("in %s gAll=%d\n", __func__, gAll);
f();
printf("agn in %s gAll=%d\n", __func__, gAll);
return 0;
}
int f(void) {
printf("in %s gAll=%d\n", __func__, gAll);
gAll += 2;
printf("agn in %s gAll=%d\n", __func__, gAll);
return gAll;
}
#include <stdio.h>
int f(void);
const int gAll = 12;
int g2 = gAll;
int main(int argc, char const *argv[]) {
printf("in %s gAll=%d\n", __func__, gAll);
f();
printf("agn in %s gAll=%d\n", __func__, gAll);
return 0;
}
int f(void) {
printf("in %s gAll=%d\n", __func__, gAll);
// gAll += 2;
printf("agn in %s gAll=%d\n", __func__, gAll);
return gAll;
}
不建議如此做
相同的變數,原生的會覆蓋全域性的變數
*返回指針的函數
tips:
編譯預處理指令:
#define:
宏:
沒有值的宏:
#define _DEBUG
這類宏是用於條件編譯的,後面有其他的編譯預處理指令來檢查這個宏是否已經被定義過了
檢查是否存在來做條件編譯程式碼
預定義的宏:
像函數的宏:
#define cube(x) ((x)*(x)*(x))
宏可以帶參數,但是參數沒有型別
帶參數的宏的原則:
一切都要括號
#define RADTODEG(x) ((x)*57.29578)
帶參數的宏:
可以帶多個參數
#define MIN(a, b) ((a)>(b)?(b):(a))
也可以組合(巢狀)使用其他宏
其他編譯預處理指令:
多個.c檔案:
專案:
編譯單元:
其他c檔案使用這個c檔案的時候,呼叫它的h檔案,是爲了告訴其他c檔案原來它的函數原型是這樣的
這個c檔案呼叫自己的h檔案是爲了檢查函數型別是否一致
標頭檔案是橋樑,是合同
#include:
""還是<>:
#include的誤區:
標頭檔案:
不對外公開的函數:
變數的宣告:
宣告和定義:
宣告是不產生程式碼的東西
定義是產生程式碼的東西
以上的都是宣告,因爲他不產生程式碼,只是告訴你有這個東西
標頭檔案:
重複宣告:
標準標頭檔案結構:
#ifndef __LIST_HEAD__
#define __LIST_HEAD__
#include "node.h"
typedef struct _list {
Node* head;
Node* tail;
} List;
#endif
printf
%[flags][width][.prec][hIL]type
Flags | 含義 |
---|---|
- | 左對齊 |
+ | 在前面放+或- |
(space) | 正數留空 |
0 | 0填充 |
width或.prec(width指整個輸出佔的位數) | 含義(用於格式靈活性) |
---|---|
number | 最小字元數 |
* | 下一個參數是字元數 |
.number | 小數點後的位數 |
.* | 下一個參數是小數點後的位數 |
#include <stdio.h>
int main(int argcm char const *argv[]) {
printf("%*d\n", 6, 123); // 123
printf("%9.2f\n", 123.0); // 123.00
return 0;
}
型別修飾(hIL,修飾type) | 含義 |
---|---|
hh | 單個位元組 |
h | short |
l | long |
ll | long long |
L | long double |
type | 用於 | type | 用於 |
---|---|---|---|
i或d | int | g | float |
u | unsigned int | G | float |
o | 八進制 | a或A | 十六進制浮點 |
x | 十六進制 | c | char |
X | 字母大寫的十六進制 | s | 字串 |
f或F | float,6 | p | 指針 |
e或E | 指數 | n | 讀入/寫出的個數 |
#include <stdio.h>
int main(int argc, char const *argv[]) {
int num;
printf("%dty%n\n", 12345, &num); // 到%n之前已經輸出了多少個字元;12345ty
printf("%d\n", num); // 7
return 0;
})
scanf
%[flag]type
flag | 含義 | flag | 含義 |
---|---|---|---|
* | 跳過 | l | long,double |
數位 | 最大字元數 | ll | long long |
hh | char | L | long double |
h | short |
type | 用於 | type | 用於 |
---|---|---|---|
d | int | s | 字串(單詞) |
i | 整數,可能爲十六進制或八進制 | […] | 所允許的字元 |
u | unsigned int | p | 指針 |
o | 八進制 | ||
x | 十六進制 | ||
a,e,f,g | float | ||
c | char |
[^.]:到逗號之前的所有東西
//$GPRMC,004319.00,A,3016.98468,N,12006.39211,E,0.047,,130909,,,D*79
// 上方是GPS模組會產生的1083協定的數據
scanf("%*[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", sTime, sAV, sLati, &sNW, sLong, &sEW, sSpeed, sAngle, sDate); // 讀取上面的數據,第一個是逗號之前的所有東西都跳過不要
printf和scanf的返回值:
用>和<做重定向
FILE:
FILE* fopen(const char *restrict path, const char *restrict mode);
int fclose(FILE *stream);
fscanf(FILE*, ...);
fprintf(FILE*, ...);
開啓檔案的標準程式碼:
FILE *fp = fopen("file", "r");
if (fp) {
fscanf(fp, ...);
fclose(fp);
} else {
...
}
#include <stdio.h>
int main(int argc, char const *argv[]) {
FILE *fp = fopen("12.in", "r");
if (fp) {
int num;
fscanf(fp, "%d", &num);
printf("%d\n", num);
fclose(fp);
} else {
printf("無法開啓檔案!\n");
}
return 0;
}
fopen:
mode | 功能 |
---|---|
r | 開啓只讀 |
r+ | 開啓讀寫,從檔案頭開始 |
w | 開啓只寫。如果不存在則新建,如果存在則清空 |
w+ | 開啓讀寫。如果不存在則新建,如果存在則清空 |
a | 開啓追加。如果不存在則新建,如果存在則從檔案尾開始 |
…x | 只新建,如果檔案已存在則不能開啓 |
文字vs二進制:
程式爲什麼要檔案:
二進制讀寫:
size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
注意FILE指針是最後一個參數
返回的是成功讀寫的位元組數
爲什麼nitem?
// student.h
#ifndef _STUDENT_H_
#define _STUDENT_H_
const int STR_LEN = 20;
typedef struct _student {
char name[STR_LEN];
int gender;
int age;
} Student;
#endif
#include <stdio.h>
#include "student.h"
void getList(Student aStu[], int number);
int save(Student aStu[]. int number);
int main(int argc, char const *argv[]) {
int number = 0;
printf("輸入學生數量:");
scanf("%d", &number);
Student aStu[number];
getList(aStu, number);
if (save(aStu, number)) {
printf("儲存成功\n");
} else {
printf("儲存失敗\n");
}
return 0;
}
void getList(Student aStu[], int number) {
char format[STR_LEN];
sprintf(format, "%%%ds", STR_LEN - 1); // 產生格式字串,%%指%
// "%19s", 20-1是因爲字串陣列最後留一個"\0"
int i;
for (i=0; i<number; i++) {
printf("第%d個學生:\n", i);
printf("\t姓名:");
scanf(format, aStu[i].name);
printf("\t性別(0-男,1-女,2-其他):");
scanf("%d", &aStu[i].gender);
printf("\t年齡:");
scanf("%d", &aStu[i].age);
}
}
int save(Student aStu[], int number) {
int ret = -1;
FILE *fp = fopen("Student.data", "w");
if (fp) {
ret = fwrite(aStu, sizeof(Student), number, fp);
fclose(fp);
}
return ret == number;
}
在檔案中定位:
long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int whence);
// whence的參數:
// SEEK_SET:從頭開始
// SEEK_CUR:從當前位置開始
// SEEK_END:從尾開始(倒過來)
#include <stdio.h>
#include "student.h"
void read(FILE *fp, int index);
int main(int argc, char const *argv[]) {
FILE *fp = fopen("student.data", "r");
if (fp) {
fseek(fp, 0L, SEEK_END);
long size = ftell(fp); // 返回當前的位置,其實在這裏就是指檔案的大小
int number = size / sizeof(Student);
int index = 0;
printf("有%d個數據, 你要看第幾個:", number);
scanf("%d", &index);
read(fp, index - 1);
fclose(fp);
}
return 0;
}
void read(FILE *fp, int index) {
fseek(fp, index*sizeof(Student), SEEK_SET);
Student stu;
if (fread(&stu, sizeof(Student), 1, fp) == 1) {
printf("第%d個學生:\n", index + 1);
printf("\t姓名:%s\n", stu.name);
printf("\t性別:");
switch (stu.gender) {
case 0: printf("男\n"); break;
case 1: printf("女\n"); break;
case 2: printf("其他\n"); break;
}
printf("\t年齡:%d\n", stu.age);
}
}
可移植性:
按位元運算:
& // 按位元的與
| // 按位元的或
~ // 按位元取反
^ // 按位元的互斥或
<< // 左移
>> // 右移
按位元與&:
按位元或|:
按位元取反~:
邏輯運算vs按位元運算:
按位元互斥或^
左移<<:
右移>>:
左移不管符號位
移位的位移不要用負數,這是沒有定義的事情
輸出一個數的二進制:
#include <stdio.h>
int main(int argc, char const argv[]) {
int number;
scanf("%d", &number);
unsigned mask = 1u<<31;
for (; mask; mask>>=1) {
printf("%d", number & mask? 1 : 0);
}
printf("\n");
return 0;
}
位元欄:控制多個bite
struct {
unsigned int leading: 3; // 冒號後面的數表示這個成員佔幾個bite
unsigned int FLAG1: 1;
unsigned int FLAG2: 1;
int trailing: 11;
};
#include <stdio.h>
void prtBin(unsigned int number);
struct U0 {
unsigned int leading: 3;
unsigned int FLAG1: 1;
unsigned int FLAG2: 1;
int trailing: 27;
};
int main(int argc, char const *argv[]) {
struct U0 uu;
uu.leading = 2;
uu.FLAG1 = 0;
uu.FLAG2 = 1;
uu.trailing = 0;
printf("sizeof(uu)=%lu\n", sizeof(uu));
prtBin(*(int*)&uu); // 取得uu的地址然後把struct U0型別的指針強制轉換爲int型別指針,並取得地址所指向的值
return 0;
}
void prtBin(unsigned int number) {
unsigned mask = 1u<<31;
for (; mask; mask>>=1) {
printf("%d", number & mask? 1 : 0);
}
printf("\n");
}
位元欄: