#include <Wire.h> // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h>
// addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address
int pos[] = {0,0};
char *s = "OPQR", *t = "TCGS";
void setup(){
pos[0] = 0;
pos[1] = 0;
bgBlink();
lcd.backlight();
lcd.setCursor(0,0);
}
void loop(){
showMsg(pos, s);
pos[0] = 1;
pos[1] = 1;
showMsg(pos, s);
delay(5000);
}
void bgBlink(){
lcd.begin(16,2);// initialize the lcd for 20 chars 4 lines and turn on backlight
for(int i = 0; i< 3; i++){
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
}
}
void showMsg(int *pos, int *arrt){
//(col,row)
lcd.setCursor(pos[0],pos[1]);
lcd.print(arrt[0]);
lcd.print(",");
lcd.print(arrt[1]);
}
void showMsg(int *pos, char *c){
lcd.setCursor(pos[0],pos[1]);
while(*c)
lcd.print(*c++);
}
//字串相加(String Concatenation)
//將"abc"加上"def"變成"abcdef"
void strcat(char *s1, char *s2) {
int i, j;
for (i = strlen(s1), j = 0; s1[i] = s2[j]; i++, j++) ;
}
//字串長度
int strlen(char s[]) {
int i;
for (i = 0; s[i] != 0; i++)
;
return i;
}
int strlen(char *s) {
int i;
for (i = 0; *s != 0; i++, s++)
;
return i;
}
字元與字串
一、字元與 ASCII 碼
一個英文字母、數字或其他的符號,我們稱它為字元。要表示一個字元,我們可以用一對單引號 ' 把該字元夾起來,例如:
char c='a';
而要在 scanf 及 printf 讀入及印出一個字元則是使用 %c。除此之外,在 C 語言中也有一些特殊字元,通常以反斜線 \ 開頭:
'\0' 空字元,用於字串的結束
'\n' New Line,換行符號
'\r' Carriage Return,回歸鍵(即 Enter 鍵)
'\t' Tab,跳格
'\b' Backspace,倒退鍵
'\a' Bell,嗶一聲
'\\' 反斜線 \
'\'' 單引號 '
'\"' 雙引號 "
事實上,字元在電腦中是以一個八位元的整數來儲存(即 1 Byte),而這個符號與數字的對應關係我們稱為 ASCII 碼 (American Standard Code for Information Interchange),例如:
也就是說,其實字元也是一個數字,因此也可以拿來做加減乘除等四則運算。例如
char c='a';
c=c+3;
printf("%c", c);
上面的例子會印出字元 d。至於下面的程式可以印出 A 到 Z 及它們的 ASCII 碼值:
#include <stdio.h>
void main(){
char c;
for(c='A'; c<='Z'; c++)
printf("%c %d\n", c, c);
}
二、字串
接下來,我們討論到字串,字串就是一段文字,我們可以用一對雙引號 " 把該段文字夾起來。而要在 scanf 及 printf 讀入及印出一個字串則是使用 %s。由於 C 語言中並沒有字串的變數型態,而是用字元的陣列來儲存一個字串,例如:
char s[20], t[20]="TCGS";
scanf("%s", s);
printf("%s", s);
上面的例子宣告了 s 是一個字串,長度為 20 個字元。我們在宣告字串的時候要注意它的長度,以免位數不夠造成程式錯誤。事實上,每個字串後面都有一個 '\0' 的字元,也就是說,上面的 "TCGS" 字串,事實上總共用了 5 Bytes,這一點要特別注意。另外,由於 s 本身就是這個字元陣列的位址,所以在 scanf 裡我們不需要加上 & 符號。
不過,用 scanf 讀取字串時,遇到空白字元便會結束,例如輸入 "Hello! TCGS!",則讀進來的字串只有 "Hello!" 而已。為了避免這個情況,我們再介紹兩個函數 gets 、puts (也是定義在 stdio.h),gets 的功能是讀入字串,而 puts 則是印出字串:
gets(s);
puts(s);
上面要注意的是,如果是使用 gets,會連換行一起讀進來,也就是字串後面會多一個 '\n' 字元。也由於 C 語言中的字串是字元的陣列,所以如果我們要知道第 N 個字元,就是用 s[N-1] 的方式取得。不過,如果我們要做字串的比對、字串串接、字串長度等功能,沒有運算子可以使用,故必須使用函數來處理。和字串相關的函數都是定義在 string.h 中,常用的字串函數如下:
strcpy(s1, s2) 將 s2 的內容複製到 s1
strcmp(s1, s2) 比較 s1、s2 的內容,如果相等傳回 0
strcat(s1, s2) 將 s2 串接到 s1 後面
strstr(s1, s2) 傳回 s2 字串在 s1字串中第一次出現的位置
strlen(s1) 傳回 s1 的長度(不含 '\0' 字元)
strrev(s1) 將 s1 字串倒置
三、編碼問題
在第二次世界大戰中,德軍的通訊編碼被美國破解,以致於機密被美國竊聽而慘敗。德軍的編碼規則(假)如下:將訊息每個字母往後推兩位再傳出去,例如 A→C、B→D,而後面的 Y→A、Z→B,所有的訊息都是大寫字母。而收到訊息的則是將每個字母往前推兩位,例如 C→A、D→B,而前面的 A→Y、B→Z。假設你是美軍的情報軍,要快速編碼及解碼德軍的訊息,希望完成一個程式,第一個字元為 + 代表要編碼,第一個字元為 - 代表要解碼,程式執行如下(粉紅色為輸入、淺藍色為輸出):
+FIRE
HKTG
-UVQR
STOP
整個程式如下:
#include <stdio.h>
#include <string.h>
void main(){
char s[80];
int i;
scanf("%s", s);
if(s[0]=='+')
for(i=1; i<strlen(s); i++) {
s[i]+=2;
if(s[i]>'Z')
s[i]-=26;
}
if(s[0]=='-')
for(i=1; i<strlen(s); i++) {
s[i]-=2;
if(s[i]<'A')
s[i]+=26;
}
printf("%s\n", s+1);
}
PS.上例中,s+1 代表從 s[1] 開始的字串。
Abstract
C語言有兩種字串宣告方式char s[]和char *s,兩者有什麼差異呢?
Introduction
char s[] = "Hello World";
char *s = "Hello World";
皆宣告了s字串,在C-style string的函數皆可使用,但兩者背後意義卻不相同。
char s[] = "Hello World";
的s是個char array,含12個byte(包含結尾\0),"Hello World"對s來說是initializer,將字元一個一個地copy進s陣列。
char *s = "Hello World";
的s是一個pointer指向char,由於"Hello World"本身就是一個string literal,所以s指向"Hello World"這個string literal的起始記憶體位置。
做個簡單的實驗證明兩者不同
#include <iostream>
using namespace std;
int main() {
char s1[] = "Hello World";
char *s2 = "Hello World";
cout << "size of s1: " << sizeof(s1) << endl;
cout << "size of s2: " << sizeof(s2) << endl;
}
執行結果
size of s1: 12
size of s2: 4
s1是陣列,所以占了12 byte,而s2只是pointer,所以占了4 byte,實驗結果與預期相同。
實際使用有什麼不同嗎?兩種寫法皆可使用substring和pointer寫法,但只有char *s可以直接使用*s++寫法。
char s[]
#include <iostream>
using namespace std;
int main() {
char s[] = "Hello World";
for(int i = 0; i != 11; ++i) {
cout << s[i];
}
cout << endl;
for(int i = 0; i != 11; ++i) {
cout << *(s + i);
}
cout << endl;
}
執行結果
Hello World
Hello World
char *s
#include <iostream>
using namespace std;
int main() {
char *s = "Hello World";
for(int i = 0; i != 11; ++i) {
cout << s[i];
}
cout << endl;
for(int i = 0; i != 11; ++i) {
cout << *(s + i);
}
cout << endl;
}
執行結果
Hello World
Hello World
但卻只有char *s可以使用*s++寫法。
#include <iostream>
using namespace std;
int main() {
char *s = "Hello World";
while(*s)
cout << *s++;
}
執行結果
Hello World
理由何在呢?
char s[]為陣列,雖然s = &s[0],但s是『常數』,恆等於&s[0]無法改變,但char *s為pointer,指向s[0],但卻是變數,可以任意改變,故可用*s++任意更改pointer值。
Conclusion
一般人很少分辨char s[]和char *s的差異,大部分狀況下用法相同,但char *s速度略快,因為不需copy的動作,且*s++為C語言常用的寫法,只有char *s才支援。