1. 程式人生 > >iOS開發——c語言——scanf函式詳細講解

iOS開發——c語言——scanf函式詳細講解

scanf的使用詳解

1、簡介

scanf函式是標準的格式化輸入函式,也就是從標準輸入裝置(鍵盤) 讀取輸入的資訊。在stdio.h中宣告,因此要使用scanf函式,必須加入 #import <stdio.h>。使用scanf時候時,需要傳入變數地址作為引數,且scanf函式會等待標準輸入裝置(鍵盤)輸入資料,並將輸入的資料賦值給變數地址對應的變數。且返回成功賦值的資料項,如果遇到錯誤或遇到end of file,返回值為EOFscanf是從標準輸入(stdin)緩衝區中讀取輸入的資料)

2、基本用法

(1)、語法格式:

scanf(“格式說明字串”,變數地址);(變數地址必須有效,並且與格式說明符的順序一致)

如: 

(2)、執行過程分析

使用scanf函式時,傳入的引數,第一個是格式說明字串,其它是一一對應的變數地址引數。執行scanf函式的時候,會等待標準裝置輸入,並不會往後執行程式碼。輸入完畢後,敲一下回車鍵,目的是告訴scanf函式我們已經輸入完畢了,scanf函式會將輸入的值賦一一對應的賦值給後面的地址對應的變數。

如:  int age;

        scanf(“%d”, &age);

執行到scanf函式的時候,會等待輸入。第一個引數是格式說明字串,%d,說明要求使用者輸入十進位制形式的一個整數。這裡注意第二個引數不是age變數,而是age變數的

地址&age,(&是c語言中的地址運算子。可以獲取變數地址)。當用戶輸入完一個整數後,敲一下回車鍵。scanf函式會把輸入的資料傳遞給地址對應的變數。並返回賦值成功個數。這樣就執行完畢scanf這函式,繼續後面程式碼的執行。

(3)注意點:

1》輸入資料時候,遇以下情況結束一個數據的輸入

*有間隔符合的情況下,以輸入的間隔符為結束一個數據的輸入的標識

    (有間隔符合輸入資料的時候必須新增間隔符號,否則報錯,

注意在有間隔符號的情況下,它們之間如果加入空格會造成的影響)

*無間隔符合的情況下,以tab、空格、回車為結束一個數據輸入的標準

*達到資料的寬度

*輸入非法資料

2》scanf函式接收輸入資料時,遇以下情況結束一個變數的賦值:(不是結束該scanf函式)

  *讀到自定義的間隔符

  *預設情況下,讀取到空白符,如tab、空格、回車。

  *達到制定資料的寬度

  *讀到到非法輸入,函式直接返回(後面的其它格式符都不會)

3》scanf函式結束情況

  *每一個格式符均有對應的資料,然後按回車後結束

4》讀到非法資料的處理方式:

完成輸出,按回車鍵盤後。scanf函式將輸入的資料賦值給後面對應位置的變數的時候,如果一遇到不匹配的就會直接返回,不再給後面變數賦值。如果第一個就不匹配,整個scanf函式中的,變數都不會賦值,或因緩衝區問題,賦錯誤值。

5》開頭空白符號無效(char型別除外)

當一次多個變數賦值的時候,開頭就使用空格、tab、回車,無效,只有在輸入資料後面使用它們才會作為間隔或結束符合使用(char除外,對應char它們都是有效輸入資料)

3、格式說明字串詳解

(1)、一般結構:

%[*][寬度][F/N][h/l]型別字元

由三部分組成

  1》格式符(格式化說明符)

  2》空白符

  3》非空白符

(2)、三個部分的詳解:

    1》格式符(格式化說明符

<strong>%a                 讀入一個浮點值(僅C99有效)   
%A                 同上  
%c                 讀入一個字元
%d                 讀入十進位制整數  
%i                 讀入十進位制,八進位制,十六進位制整數  
%o                 讀入八進位制整數
%x                 讀入十六進位制整數  
%X                 同上 
%c                 讀入一個字元  
%s                 讀入一個字串  
%f                 讀入一個浮點數 
%F                 同上  
%e                 同上  
%E                 同上  
%g                 同上  
%G                 同上  
%p                 讀入一個指標  
%u                 讀入一個無符號十進位制整數 
%n                 至此已讀入值的等價字元數  
%[]                掃描字元集合  
%%                 讀%符號</strong>

            修飾符

*:星號 ,用於空讀一個數據。

              百分號(%)與格式符之間的星號(*)表示讀指定型別的資料但不儲存

        L/l 長度修飾符               輸入"長"資料

      h 長度修飾符                 輸入"短"資料

      W 整型常數                   指定輸入資料所佔寬度

           hh,ll同上h,l但僅對C99有效

  2》空白符

空白字元,會使scanf函式讀操作中略去輸入中一個或多個空白符,空白符可以是空格、tab、回車

    3》非空白符

非空白字元,會使scanf函式讀操作中剔除與這個非空白字元相同的字元。但在輸入時必須輸入這些字元。否則就會出錯 。一般作為間隔符合使用。

 (2)、使用例子:

1》常用比較難的格式符例子:

*%f:用於接收或輸出一個小數  

printf(“<%f>",12.3456789);

輸出為:<12.345679>

總結:預設輸出6位小數,且如果原資料有第七位小數,四捨五入給第六位

float a;

scanf(“%f",&a); // 接收一個小數

printf(“<%f>",a); //輸出接收的資料

輸入:3.1234567  (注意有7位小數)

輸出:3.123457

*%e的使用:%e是以指數形式輸出或讀起一個小數

             例如:1.256e2 就是1.256*10的2次方  

<strong>printf("%e",1123.45678); 
輸出:1.123457e+03 // 預設輸出格式
//總結:預設輸出6位小數。如果有第七位小數,四捨五入給第六位

float a;
scanf(“%e",&a);
printf(“%e",a);
//輸入:123.456789 ,輸出 1.234568e+02
</strong>

2》寬度修飾符號的常用例子:%m.nX——》x代表格式符

                * %m.nf/e——》m控制資料的整個長度,n控制小數的位數, n與m之間採用“.”作為分隔符號

                      與printf函式一起使用: 

m控制整個輸出資料的長度,如果m大於輸出的資料長度,在資料前面使用空格填充,如果m小於預設輸出資料的長度,m失效,而n控制小數的長度。其它格式:%mf/e、%.nf/e、%m.nf/e
printf(“%.5f",5.123456789); ——》n是5表示輸出5位小數
輸出:5.12346
printf(“%4f",5.123456789); ——》m長度為4,小於預設輸出資料的整體長,無效,使用預設輸出
輸出:5.123457 (預設輸出長度是,保證小數是6位,對於f而言)
printf("<%9f>",5.123456789); —》m大於預設輸出的長度,前面使用空格填充
輸出:< 5.123457>
printf(“<%9.4f>”,5.123456789);——》同時使用m和n,之間使用.間隔
輸出:<   5.1235>


                     與scanf函式一起使用:(沒有精度控制) 

只能有%mf/e這種格式,不能有%m.nf/e 或 %.nf/e,裡面的“.”符號無效。但能控制讀取的位數,m如果小於輸入數,直接擷取m位讀取進去,如果m大於輸入數,按原數讀
例子:
float a;
scanf(“%2f”,&a); // m是2,只讀取前2位數,
printf(“%f",a);
輸入:123.4567 ——》m是2,只讀取前2位數
輸出:12.000000

scanf(“%.3f",&a); // 報嚴重警告”.”是個無效符號(Invalid conversion specifier ‘.’)
scanf(“%3.3e",&a); 也同樣警告


3》%m.nd的使用

printf(“<%.9d>”,1234567); //.n格式:n如果大於整數長度,預設使用0
// 輸出:<001234567>
printf(“<%.4d>”,1234567); //.n格式:如果n小於其長度,n失效
// 輸出:<1234567>
printf(“<%9d>”,1234567); //  m格式,如果大於整數的長度,使用空格填充
// 輸出:<  1234567>
printf("<%4d>",1234567); //如果m小於其長度,m失效
// 輸出:<1234567>  
printf(“<%9.9d>",1234567); // 都相同的情況下,使用.n的效果
// 輸出:<001234567>
printf(“<%9.8d>",1234567); // 先採用前面的9,再採用後面的.8格式
// 輸出:< 01234567>

int a;
scanf(“%3d",&a); //m小於輸入數,只擷取m長度的數
printf("<%d>",a); 
//輸入:1234567
//輸出:<123>
int b;
scanf(“%8d",&b); //當m大於輸入數,m失效
printf(“<%d>",b);
//輸入:123
//輸出:<123>
scanf(“%.4d",a); // 不可以,報嚴重警告:點”.”無效符號<strong style="font-family: Helvetica; letter-spacing: 0px;">                             </strong>

*scanf中沒有精度控制

(4)、注意點

1》不可使用%f接收double型別的資料 ,會報下面的警告

        預設情況下,a、f、e 和 g 告訴 scanf() 為 float 分配資料。 如果將 L / l放在這些修飾符的前面,則 scanf() 為 double 分配資料。使用 L 就是告訴 scanf(),接收資料的變數是 long double 型變數。小寫l,表示普通的double型別

注意大寫L,也是不可以接收double,而是long double型別 

正確寫法  

 long double a;
 scanf(“%Lf",&a); // L修飾表示long 
 printf(“<%Lf>”,a);// 輸出也要使用L
double a;

scanf(“%lf",&a); // 使用小寫l,表示普通doule

2》引數是變數的地址,使用&取地址符(陣列除外,不用&)     

int num = 0;
scanf("%d", num);
printf("%d", num);
printf("請輸入數字\n");

上面錯誤,修改為:

<strong>int num = 0;
printf("請輸入數字\n");
scanf("%d", &num);
printf("%d", num);</strong>

3》如果只是定義一個指標,沒有初始化,也就是沒有指向具體陣列。不可以拿來直接和scanf使用,原因:只是定義和分配了指標本身變數,而沒有分配具體儲存內容的空間(也就是沒有任何指向指向)

        如:

        char *name; 

          scanf(“%s”, name);  是錯誤的,需要先指向具體陣列

4、掃描字元集合的使用:%[] 

(1)、基本認識,只能使用在

    1》 基本瞭解:

掃描集(scanset),是scanf後來新增的一個新的特性。

掃描集定義一個字元集合,只有在掃描集範圍內的字元,才會被scanf讀取並賦值給對應的字元陣列。一旦遇到非法資料,立刻返回

如:

scanf(“%[abc]”,str); ——》表示只由滿足abc這三個字元才能輸入str裡面

    2》常用特殊符號:

^,放在首位,表示除了後面定義的之外的字元

    如:

scanf(“%[^abc]”, str);  ——》表示除了abc之外的字元都可以賦值給str陣列

:表示一個範圍,如:

scanf(“%[a-z]”,str);——》表示a到z範圍的小寫都可以賦值給str陣列

(注意只是小寫,掃描集合區分大小寫.%[a-zA-Z]表示大小寫都可以)

(2)、常用技巧

1》限制接收的資料

  *只接收a-z和A-Z的字母,scanf(“%[a-zA-Z]”,str);

2》接收帶空格的字串——》scanf("%[^\n]", name);

<strong>                   
#include <stdio.h>
int main(int argc, const char * argv[])
{
    char name[20];
    printf("請輸入字串\n");
    scanf("%[^\n]", name);
    printf("%s\n", name);
    return 0;
}</strong>

請輸入字串

hello world!入)

hello world!    (輸出)

 分析%[^\n]這個掃描集:

^表示非、除了的意思,^\n表示除了“\n”都可以被scanf讀取並賦值給字元陣列。

3》stdin的緩衝區清空——》scanf(“%*[^\n]%*c”);清除一段文字(後面詳細說)

(3)、注意點

1》因為掃描集,賦值的時候會以字串的形式賦值給對應的字元陣列,可使用%s直接輸出,結果。而不用像c語音的陣列遍歷那樣(所有注意輸入的長度長度,不要多於陣列元素個數) 

<strong>#include <stdio.h>
#include <string.h>
int main(int argc, const char * argv[]){
    char str[10];
    printf("請輸字串\n");
    scanf("%[a-zA-Z]", str);
    printf("str1=%s\n", str); // 可這樣直接輸出
    printf(“輸入的字元長度:%zd\n",strlen(str)); 
    for(int i = 0; i<strlen(str);i++){ 
        printf("str[%d]=%c\n", i, str[i]);
    }
        return 0;
}

/*
輸入:aaaaaaaaaaaaaaaa
輸出:str1=aaaaaaaaaaaaaaaa
  輸入的字元長度:16
str[0]=a,str[1]=a,str[2]=a,str[3]=a,str[4]=a,
  str[5]=a, str[6]=a,str[7]=a,str[8]=a,str[9]=a,
             str[10]=a,str[11]=a,str[12]=a,str[13]=a,str[14]=a,str[15]=a,
*/</strong>

2》%[]對間隔符的問題  

<strong>//空白符,也會當作內容讀取(類似給char賦值),如果字符集中沒有空白符號,就會不匹配而返回

#include <stdio.h>
int main(int argc, const char * argv[]){
    char str1[10], str2[10];
    printf("請輸字串\n");
    scanf("%[a-zA-Z]%[a-zA-Z]", str1, str2); // 這裡沒有任何分隔符,所以str2永遠也不會符合值的
    printf("str1=%s\n", str1);
    printf("str2=%s", str2);
    return 0;
}

/*
輸入:aaa□□bbbb 
輸出:str1=aaa
     str2=
分析:
掃描集合的使用中,會和單個字元賦值的時候,對空格符合的處理是一樣的,就是會把它們也當作賦值內容的一分,而不是類似%d等型別符那樣”忽略它們“。在讀到a後面的一個□,會在str1的掃描集內比對,結果不滿足其範圍,這樣scanf就相當於讀取到非法資料,直接返回。這樣就不會給str2賦值。
也有同學可能認為,如果分開寫,這樣“空白格符”會作為預設的間隔符使用嗎?回答依然把“空白符”作為可賦值內容的一部分使用(只要存在了stdin的快取問題)
scanf("%[a-zA-Z]%[a-zA-Z]", str1, str2); (分開寫成下面方式,也是無效的)
scanf("%[a-zA-Z]", str1);scanf("%[a-zA-Z]", str2);
*/</strong>
<strong>// 如果定義的分隔符,在掃描集內,就會使間隔符失效。賦值發生錯誤
#include <stdio.h>
int main(int argc, const char * argv[])
{
    char str1[10], str2[10];
    printf("請輸字串\n");
    scanf("%[a-zA-Z,],%[a-zA-Z,]", str1, str2); // 這裡兩個掃描集,以“,”分隔
    printf("str1=%s\n", str1);
    printf("str2=%s", str2);
    return 0;
}
/*
輸入:aaa,bbb   
輸出:str1=aaa,bbb 
    str2=
結果分析,scanf對str1賦值操作中,把“,”當作自己的普通字元,賦給str1。這樣stdin中的”,“就被scanf讀取出來。而後面找不到與“,”這個分隔符匹配的字元,會造成匹配不到“,”這個字元的錯誤,scanf會直接返回。
,
// 如果分隔符,不在掃描集內,輸入的時候必須寫分隔符
#include <stdio.h>
int main(int argc, const char * argv[]){
    char str1[10], str2[10];
    printf(“請輸字串,使用#作為分隔符合\n”);
    scanf("%[a-zA-Z]#%[a-zA-Z]", str1, str2); // 採用#作為分隔符合
    printf("str1=%s\n", str1);
    printf("str2=%s", str2);
    return 0;
}
/*
輸入:aaabbb#cccddd  (使用#分隔) 
輸出:str1=aaabbb
     str2=cccddd
分析:
使用#作為分隔符合,就必須要遵守。如果輸入別的輸入符合,或不滿足的字元,scanf會自動返回,不會再繼續賦值:
輸入:aabb*cc#dd   (scanf只要讀取到不滿足格式的字元,會立刻返回,而不會繼續賦值)
輸出:str1=aabb
str2=
分開寫 scanf("%[a-zA-Z]#%[a-zA-Z]", str1, str2);也會有上面效果
scanf("%[a-zA-Z]#", str1);
scanf("%[a-zA-Z]", str2)
(注意分漢字和英文情況輸入)
*/
</strong>

5、一次給多個變數賦值:

(1)、簡介:

格式說明符可以由多個格式符組成,每個格式符都對應一個相同型別變數地址。scanf函式可以把輸入的資料,根據格式符,依次賦值給相同型別的變數。預設使用tab、空格、回車作為間隔,也可以自己定義間隔符合如“,”、“-”、“#”等。但是要注意最後還是以回車鍵作為輸入結束

(2)、常用例子:

1》使用scanf函式接收3個數

*預設中間不使用任何間隔符合 

<strong>int a= 1 ,b= 1, c =1;
 scanf("%d%d%d",&a,&b,&c);       
 printf("%d,%d,%d",a,b,c);</strong>
/*cccc
     1、字串的建立 (一般物件方法不用都是使用類方法)
     直接賦值方式 (常用) 
     alloc 、initxxx 偶爾用
     類方法 (常用):常用於拼接字串、把基本資料型別轉換為字元型別
     
     注意: string是不可變字串,如果初始化沒有賦值,就會為空,不可新增,只能更改指向
     */
    NSString *s1 = @"abcdef"; // 直接賦值
    NSString *s2 = [[NSString alloc] initWithFormat:@"aabbcc%d",10]; //alloc、initxx
    NSString *s3 = [NSString stringWithFormat:@"age is %d", 10];     //類方法
    s1 =  @"abcdf"; // 直接修改指向
    
    /*
     2、對資料轉換的操作
     1》基本資料型別轉換為字串
     2》常用結構體型別轉換為字串
     3》oc和c字串的轉換
     */
    // 基本資料型別轉換為字串
    NSString *numStr = [NSString stringWithFormat:@"age is %d", 10]; // int型別轉換為字串型別
    NSString *doubleStr = [NSString stringWithFormat:@"高度是: %.2f", 10.0]; // double轉換為字串型別
    
    // 結構體轉換為字串型別(是函式方法,不是string物件或類方法)
    CGRect rect = CGRectMake(10, 10, 10, 10);
    NSString *rectStr =  NSStringFromRect(rect); // 結構體轉換為字串 (函式方法)
    
    //oc 和 c 字串相互轉換
    NSString *s4 = [[NSString alloc] initWithUTF8String:"abcd"];     // 將c字串轉換為 oc字串
    NSString *s5 = [NSString stringWithUTF8String:"abcd"];
    const char *str5 = [s3 UTF8String];                               // oc轉換為c字串
    
    /*
     3、常用對其內容的操作
     1》是否包含某個字串->rangeOfString
     2》遍歷: length 、characterAtIndex
     3》判斷是否包含數字
     */
    
    // 判斷字串是否包含某個字串(根據location是否是:NSNotFound)
    NSString  *str = @"abcd efgh jk";
    NSRange range = [str rangeOfString:@"bc"];
    if (range.location == NSNotFound) {
        NSLog(@"沒有");
    }else{
        NSLog(@"有");
    }
    
    
    NSLog(@"-----字串的遍歷,並輸出-------------");
    // 遍歷,characterAtIndex 根據索引值獲取對應的字母,返回是字元型別
    str = @"我們";
    NSLog(@"length = %zd", str.length); // 結構是:2 (返回的是字元個數,而不是位元組數)
    
    str = @"abcd 1 cd0e 2";
    for (int i = 0; i<str.length; i++) {
        NSLog(@"%d ,%c",i,[str characterAtIndex:i]);
    }
    NSLog(@"-----判斷是否有數字,並取出-------------");
    
    // 判斷是否包含數字 ——》根據asc碼錶種數字對應的值來判斷
    for (int i = 0; i<str.length; i++) {
        if ([str characterAtIndex:i] >= '0' && [str characterAtIndex:i] <= '9') { // 必須是字元型別,根據的是asc碼錶種數字對應的值來判斷
            NSLog(@"%c",[str characterAtIndex:i]);
        }
    }
    /*
     4、對檔案的操作
     1》檔案的讀取
     2》檔案的寫入
     */
    NSLog(@"-----讀取操作檔案(文字路徑)-------------");
    // 根據全路徑,讀取文字檔案
    NSString *path = @"/Users/an1911/Desktop/經典程式碼/Founcation框架的重點知識點/讀取寫入檔案/readme.txt";
    NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"\n%@",content);
    
    NSLog(@"-----讀取操作網路資源(也可以讀取檔案)-------------");
    /*
     URL : 資源路徑 (可以讀取文字檔案,但資源路徑不可以有中文)
     協議頭://路徑
     file://文字路徑
     http://網址
     */
    NSURL *url = [NSURL URLWithString:@"file:///Users/an1911/Desktop/經典程式碼/Founcation框架的重點知識點/讀取寫入檔案/readme.txt"]; // 不可包含漢字,且必須是全路徑,包括字尾名
    NSLog(@"有中文的url = %@",url); // url為空
    content = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"有中文——》\n%@",content);
    
    url = [NSURL fileURLWithPath:@"/Users/an1911/Desktop/經典程式碼/Founcation框架的重點知識點/讀取寫入檔案/readme.txt"]; // 如果確定是文字,可用這種方式()
    NSLog(@"fileURLWithPath——》地址:\nurl = %@",url);
    content = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"fileURLWithPath——》讀取的內容:\n%@",content);
    
    url = [[NSURL alloc] initWithString:@"file:///Users/an1911/Desktop/readme.txt"];
    NSLog(@"無中文——》url = %@",url);
    content = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"\n%@",content);
    url = [NSURL URLWithString:@"http://www.baidu.com"];
    //content = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
    // NSLog(@"\n%@",content);
    
    
    
    NSLog(@"-----寫入操作文字檔案(物件方法)-------------");
    
    NSString *writeStr = @"JAVA1\nJAVA2"; // 使用\n作為寫入的換行
    [writeStr writeToFile:@"/Users/an1911/Desktop/write.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    url =[NSURL fileURLWithPath:@"/Users/an1911/Desktop/write2.txt"];
    // url = [NSURL URLWithString:@"file:///Users/an1911/Desktop/write2.txt"];
    [writeStr writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:nil];


注意:輸入的時候,中間使用空格、tab、回車作為間隔符合,直到輸入完三個資料

然後按回車,結束輸入,並賦值

*使用自己定義的間隔符合 

<strong> int a= 1 ,b= 1, c =1;
        
 scanf(“%d,%d,%d”,&a,&b,&c);
        
 printf("=%d,%d,%d=",a,b,c);
</strong>

注意:我們在輸入資料的時候,就必須帶上“,”作為資料間的間隔,如果使用其它間隔符合,會造成賦值的錯誤,如輸入:11,12,13 ,然後回車就會把資料依次賦值

    或3,□4,□5 ↙(輸入a,b,c的值)

            3,□□□4,□5 ↙(輸入a,b,c的值)(把%d,看作一個整體

  不可:3□□□,4□,5

 2》接收3個字元資料:(會有些莫名的錯誤——》把空格、tab、回車作為資料賦值,不以它們為分割符,以對應關係和字元個數結束判斷依據) 

<strong> char a ,b, c;

 scanf(“%c%c%c",&a,&b,&c);

 printf("<%c,%c,%c>",a,b,c);
</strong>

注意:這裡我們是預設沒有間隔符,但是因為scanf在給char型別的資料賦值的是,會把空格、tab、回車或其它任何符合如“,”、“#”等都當作一個char資料賦值給後面的地址引數對應的變數。所以這裡特別注意,如果我們輸入資料的時候,以空格、tab、回車等作為間隔的時候,實際上是把它們賦值給了後面的變數地址對應的變數,這樣我們的資料就會混亂,

輸入: a回車b回車——》當第二個回車的時候,就會當作輸入結束

輸出:<a,

  ,b>

分析:這裡會把輸入的第一回車當作,第二個字元資料。當我們輸入完三個字元資料後,我們再按回車的時候,系統會認為我們已經輸入完了三個字元資料,從而結束輸入,並賦值變量,把a賦值給變數a,回車賦值給b,把b賦值給b變數。(注意輸入結束點,就是輸入資料個數和格式符個數一致了)

正確輸入:直接輸入連續的字元,以回車結束

輸入:abc回車

輸出:  <a,b,c>

            所以,接收多個字元資料的時候,如果沒有預設的分割符。我就直接輸入對應個數的連續字元即可:

*使用間隔符 

<strong>char a ,b, c;
        
scanf("%c,%c,%c",&a,&b,&c);
        
printf(“<%c,%c,%c>”,a,b,c);</strong>

輸入:a,b,c     輸出:a,b,c

注意一個細節:如果我們是在輸入法是漢字的情節下,按亮caps lock鍵,我們看到的控制檯輸入的是小寫,但輸出的是大寫。因為這是一個大寫的鍵。所以切換為英文格式,這樣我們看到輸入是小寫,輸出的也是小寫

難點:如果我們使用多個scanf接收資料的時候,前面遺留的資料會造成後面對字元資料接收的錯誤影響,所以一般還要在最後新增fflush(stdin);這個函式,清空輸入流,後面介紹

6、接收字串(c語言中必須使用字元陣列作為接收變數,且要確定元素個數)

(1)、%s的基本使用——》不可接收帶空格的字串

1》單個%s的基本使用——》不需要使用&

  

注意1:

%s只是接收一個字串,如果我們輸入字串的時候,中間有空格、tab,這兩個預設的間隔符,其後面的字元不會賦值給地址對應的陣列變數,而是當作無用資料給截去。且只有按回車鍵就會立刻結束輸入

開始輸入 tab、空格、回車,都是被scanf給忽略(先這樣理解)

注意2:這樣接收的字串,不能包含空格。

2》多個%s的同時使用   

<strong>char name1[10];
char name2[10];
    
scanf("%s%s", name1, name2);
    
printf("%s,%s", name1, name2);
//輸入:abcdefg  hijklmn  (這裡使用空格間隔)
//輸出:abcdefg,hijklmn</strong>

*接收多個字串 ——》預設採用tab、空格、回車作為間隔符

(2)、使用掃描集,來接收字串,上面已經介紹

*注意把空白符當作其賦值內容的一部分。而%s會把空白符合當作間隔符使用

7、難點問題

(1)、stdin中的緩衝區問題——》存在於多個scanf操作中(如果是單個scanf操作只會直接返回不影響)

1》單個scanf函式,不存在快取問題,只是不匹配,就直接返回

<strong>// 一個scanf,就算是為多個數據讀取賦值操作,遇到錯誤資訊會直接返回,而不會存在stdin的快取現象,而引起的錯誤

int age;
char c;
scanf(“%d,%c”, &age,&c);
printf("%d,%c", age, c);
/*
輸入:a回車
輸出: 0,
分析:scanf函式首先讀取到的字元a,和首個格式符%d進行匹配,結果不匹配直接返回。而沒有給變數c賦值
*/
</strong>
<strong>// 修改為多個scanf,這個時候就會有快取引起問題

   int age;
    char c;
    scanf("%d", &age);
    scanf("%c",&c);
    printf("%d,%c", age, c);
/*
輸入:a回車
輸出: 0,a
分析:第一個sanf函式讀取stdin資料,首先讀到字元a,和其格式符號%d匹配,結果不匹配,直接返回,結束第一個scanf函式。但是字元”a“依然存在在stdin快取中,當第二scanf執行的時候,stdin中有資料,scanf會直接執行,讀取資料和%c匹配,滿足匹配,把字母a賦值給了變數a(下面詳解)
*/
</strong>

2》多個scanf函式,存在stdin快取問題(會把stdin裡面殘留的資料和後面型別匹配並賦值)——》scanf("%*[^\n]");和scanf(“%*[^\n]%*c”);的使用和區別  

          注意看下面的錯誤情況:

<strong>// 不安全的寫法,注意容易出錯的地方
#include <stdio.h>
int main(int argc, const char * argv[]){
    
    int age = 1;
    char name[10];
    
    scanf("%d", &age);
    scanf("%s", name);
    
    printf(“age =%d,name=%s", age, name);
    return 0;
}/*
輸入:abce回車
輸出:age=1,name=abce
分析:
第一個scanf函式在執行的時候,從stdin中讀取的資料是字串,和%d型別不匹配,直接返回,結束函式
第二個scanf函式執行的是,查詢到stdin 中有快取的資料,會自動的執行(而不是等我們再次輸出然後回車操作),讀取到字串abce和其型別符%s匹配,把從stdin中讀取的字串賦值給name,並在stdin快取中清除讀取到到內容

改錯:我們只要在第一個scanf執行完畢後,清空在stdin中的快取即可,兩種方式
1、fflush(stdin)——》存在移植性問題,在vc6中有效。但是在xcode5中無效,不建議使用
2、使用自己編寫的程式碼清空快取
       scanf(“%*[^\n]"); 清除一段字串
   scanf(“%*[^\n]%*c”);清除一段字串和其後面的空白符號——》用在後面是為%c或字符集賦值的操作(它們把空白字元當作內容處理)*/</strong>

修改為如下正確寫法:

<strong>//去除快取的正確寫法
#include <stdio.h>
int main(int argc, const char * argv[]){
    int age ;
    char name[10];
    
    scanf("%d", &age);
    scanf("%*[^\n]");
    scanf("%s", name);
    
    printf("%d,%s", age, name);

    return 0;
}
/*
輸入:abc(回車)
           def(回車)

輸出:0,def
分析,前的abc已經被scanf(“%*[^\n]”);,把stdin中的abc給清除了,沒了影響了。但是這個時候stdin中還有abc輸入完後的按的回車,在stdin中會出現對應的’\n“符號。但是%s預設以它為空白符號,不當作資料處理,所以無影響。但是如果後面是%c或字符集、%[],就必須在消除字串後,再消除回車符號,使用scanf(“%*[^\n]%*c”);
*/

</strong>

scanf(“%*[^\n]%*c”); 清除回車的情況:

<strong>//清除回車符號的重要性
#include <stdio.h>
int main(int argc, const char * argv[]){
    int age ;
    char c;
    
    scanf("%d", &age);
    scanf("%*[^\n]");
    scanf("%c", &c);
    
    printf(“<%d,%c>, age, c);

    return 0;
}
/*
輸入:abc (回車)
輸出:<0,
     >
不能為c輸入資料的,出錯分析:
第一個scanf,讀取的字串和%d不匹配直接返回,字串依然存在再stdin中
然後第二個scanf函式,讀取出了前面一段字串,但不賦值,消除了abc字串的快取影響。但是再stdin中依然存在回車符號\n,
    第三個scanf執行,stdin裡有資料\n,讀取並賦值,把\n賦值給c了

正確分析:
把第二個scanf修改為
    scanf(“%*[^\n]%*c”);,就能為c輸入字元資料
輸入:abc (回車)
     d
輸出:<0,d>

注意:我們每擊打一下"Enter"鍵,向緩衝區發去一個“回車”(/r),一個“換行"(/n),
*/
</strong>

習題一:避免因錯誤輸入造成的死迴圈 

<strong>// 注意錯誤原因
#include <stdio.h>
int main()
{
     int a;
     int b;
     do{
        printf("請輸入數字:\n");
        scanf("%d",&a);
        scanf("%d",&b);
        printf("a=%d, b=%d\n",a,b);
    }while(b!=11);
    printf("結束輸入");
}
/*
輸入:a (回車)
結果:死迴圈

分析:
stdin中一直有快取資料a,導致scanf會直接讀取並賦值操作,但是資料一直不匹配,所以a又一直存在在stdin的快取中,導致死迴圈
*/
</strong>
<strong>// 修改後的正確版本
#include <stdio.h>
int main()
{
     int a;
     int b;
     do{
        printf("請輸入數字:\n");
        scanf("%d",&a);
        scanf("%*[^\n]");
        scanf("%d",&b);
        scanf("%*[^\n]");
        printf("a=%d, b=%d\n",a,b);
    }while(b!=11);
    printf("結束輸入");
}
/*
分析:
scanf(“%*[^\n]”);會把前一個stdin中的不匹配的除了\n以外的資料都清除了。且%d不會把空格符號作為資料讀取和賦值
*/
</strong>

習題二、無法正確的輸入字元資料情況(比例題2更多的判斷,跟加安全)  

<strong>// 不安全的寫法
#include <stdio.h>
int main()
{
     int a;
     char b;
     do{
        printf("請輸入:\n");
        scanf("%d",&a);
        scanf("%c",&b);
        printf("<a=%d, b=%c,b=%d>\n",a,b,b);
   }while(b!=’n');

    printf("結束輸入");
}
/*
輸入:11 (回車)
輸出:<a=11, b=
,b=10>
           請輸入數字:

分析:回車賦值給b了

正確輸入方式:(很容易輸入出錯)
輸入:11c
回車:<a=11, b=c,b=99>
    
*/
<pre name="code" class="csharp">// 正確寫法( scanf("%*c");;)
#include <stdio.h>
int main()
{
     int a;
     char b;
     do{
        printf("請輸入:\n");
        int num = scanf("%d",&a);
         
         if(1 == num ){
             scanf("%*[ \t\n]”);
      //scanf(" “); 不安全
         }else{
           scanf("%*[^\n]%*c");
         }
         
        scanf("%c",&b);
        scanf("%*[^\n]%*c");
        printf("<a=%d, b=%c,b=%d>\n",a,b,b);
    }while(b!='n');
    printf("結束輸入");
}
/*
輸入:11 (回車)
c
或:aa (回車)
    c
或 11 (tab)(回車)
   c
或 11 (空格)(tab)c
結果都會為,b賦值為c
 */

</strong>

習題三:使用 getchar() 清除所有快取  

<strong>int main(){
    int i, c;
    
    for (;;) { // 無限迴圈
        // 表示一直的資料輸入,不管是否匹配,stdin快取區都會有資料殘留
        if ( scanf("%d", &i) != EOF ) { //如果不按ctrl +d就會一直輸入資料
            // 迴圈如果c不是\n或檔案最後,就一直使用getchar讀取除緩衝區
            while ( (c=getchar()) != '\n' && c != EOF );
        }
        printf("%d\n", i);
    }
    return 0;
}
</strong>

習題四、用“空格符”來處理緩衝區殘餘資訊

<strong>// 不安全寫法
#include <stdio.h>
int main()
{
    char str[4];
    
    for(int i=0; i<5; i++){
    
        scanf("%c",&str[i]);
    }
    printf("%s",str);
    
    return 0;
}
/*
輸入: a b c d e(回車)
輸出: a b c
或
輸入:a(回車)
b(回車)
c(回車)
輸出:a
b
c
*/
</strong>
<strong>// 正確寫法
#include <stdio.h>
int main()
{
    char str[4];
    
    for(int i=0; i<5; i++){
    
        scanf(" %c",&str[i]);
    }
    
    printf("%s",str);
    
    return 0;
}
/*
分析
 scanf(" %c",&str[i]);會把所以的空格符號都給讀取出來,清空stdin的快取
 和:scanf("%*[ \t\n]”);效果一樣

輸入:a b c d e(回車)
輸出;abcde
*/
</strong>

(2)、多個%d賦值中,回車的不同情況(輸入非法資料,回車立刻結束輸入)

<strong>//多個%d賦值,輸入資料的時候,回車符號的特殊情況
    int age;
    int age1;
    int age2;
    char c;
    scanf("%d%d%c", &age1, &age2,&c);
    
    printf(“%d,%d,%c", age1, age2,c);
// 或
    int age1;
    int age2;
    scanf("%d", &age1);
    scanf("%d", &age2);

/*
分析:
如果是匹配的資料,回車、空格、tab鍵都會當作間隔符號使用(多個%d的情況下)
輸入:11回車          或輸入 11 12 c
  12回車
輸出:11,12             輸出:11,12      
分析:這裡因為是匹配資料,所以回車可以當作間隔符號使用,但是這裡注意char型別接收的型別中包括所有空白符號,所以char變數c不是沒有輸出,而是輸出的是“\n”(注意理解)


如果輸入的是不匹配資料,空格和tab還是可以輸入,而回車會直接導致執行完畢,而不再是間隔符(多個%d的情況下)
輸入:a □11□□□□ c回車
輸出:1,2,
分析:輸入的時候採用空格和tab鍵為間隔符合,當我們認為輸入完三個資料的時候,按回車,結束輸出。執行scanf函式讀取並賦值。但是讀取的第一個資料是a,而型別符是%d不匹配直接返回,結束scanf函式
輸入:a回車
輸出:1,2,
分析:如果我們輸入的是不匹配的資料,如果按回車,會直接執行scanf完畢。而不再以回車鍵為分隔符合,然後輸入另外的資料。和上面的第一種情況注意區分

*/
</strong>

8、經典程式碼總結——》在實現功能的基礎上,精簡程式碼、保證資料安全性

(1)、接收使用者輸入兩個數,並計算它們的和值

(這個功能很好實現,難點在於如何保證使用者如果輸入不正確的資料,程式能夠判斷出並給出相應的處理1、不輸出錯誤資料,2、保證繼續迴圈告訴使用者輸入資料,而不是隻執行一次,3、保證不死迴圈)  

<strong>#include <stdio.h>
int main()
{
    int a,b; // 接收資料的變數
    int count; // scanf返回的成功項
    char isErro = 'N'; //提示語音的判斷
    while(count != 2){
        
        if('N' ==isErro){
            printf("請輸入兩個數(使用逗號隔開)\n");
        }else if('Y' == isErro){
            printf("輸入錯誤資料,請重新輸入兩個數(使用逗號隔開)\n");
        }

        count = scanf("%d,%d", &a, &b);
        scanf("%*[^\n]%*c"); // 清除stdin中的快取資料(如果是給%c和%[]賦值,需要更多的判斷)

        if(2 == count){ //如果兩個變數數賦值成功
            int sum = a+b;
            printf("和是:%d\n",sum);
        }else{
            isErro = 'Y';
        }
    }
    return 0;
}
</strong>
<strong>// 精簡的程式碼
#include <stdio.h>
int main()
{
        int a,b,c; /*計算a+b*/
        while(scanf("%d,%d",&a,&b)!=2)scanf("%*[^\n]%*c");
        c=a+b;
        printf("%d+%d=%d",a,b,c);
                                                           
    return 0;
}
</strong>

補充:

對EOF的基本認識

while(scanf(“%d”,&n)!=EOF));

標題那段程式碼的意思是,輸入Ctrl+z終止迴圈(這是在Windows下,在Unix環境下是Ctrl+d)。如果你輸入字元a,而迴圈體裡又沒有getchar之類讀字元的函式,就會死迴圈,因為a會一直留在輸入緩衝區中。要想在輸入錯誤的情況下終止把 !=EOF 去掉就行了,即成功輸入的個數為0的情況下推出迴圈。