... <看更多>
c語言puts 在 C語言-檔案I/O | 鋼彈盪單槓 的推薦與評價
這篇是講關於C語言如何操作檔案. ... puts("來源檔案開啟失敗"); return 1; } FILE *file2 = fopen("target.txt", "w"); if(!file2) { ... <看更多>
Search
這篇是講關於C語言如何操作檔案. ... puts("來源檔案開啟失敗"); return 1; } FILE *file2 = fopen("target.txt", "w"); if(!file2) { ... <看更多>
C語言 庫函數 int puts(const char *str) 將一個字符串寫入到stdout,但不包括空字符。一個換行符被追加到輸出。 聲明. 以下是 puts() 函數的聲明。 int puts(const ...
#2. putchar、getchar、puts、fgets - OpenHome.cc
char buf[20] 這行宣告一個可以容納20 個字元的字元陣列,這是C 語言中儲存字串的方式,之後還會介紹,超過 fgets 指定長度的部份,會留在輸入緩衝區,執行結果如下:
#3. C语言puts函数用法详解
puts函数,C语言puts函数用法详解 ... int puts(const char *s);. 这个函数也很简单,只有一个参数。s可以是字符指针变量名、字符数组名,或者直接是一个字符串常量。功能是 ...
C語言 stdio頭文件(stdio.h)中puts函數的用法及代碼示例。 用法: int puts ( const char * str ) ...
puts函數,C語言puts函數用法詳解 ... int puts(const char *s);. 這個函數也很簡單,只有一個引數。s可以是字元指標變數名、字元陣列名,或者直接是一個字 ...
#6. c++ puts函数_在C / C ++中使用puts()函数 - CSDN博客
今天,在本教程中,我们将讨论C和C ++编程语言中广泛使用的puts()函数。 Even though the printf() and cout functions in both C and C++ are ...
C語言 裡的puts()函式怎麼用 ... puts就是輸出字串啊。 ... * to write a string to stdout. ... Hello world from puts! 你要輸出換行的話,就用puts( "\n" );
#8. C語言scanf()和gets()及printf()和puts()的區別
C語言 scanf()和gets()及printf()和puts()的區別 ... #include<stdio.h> int main() { char a[10],b[10]; char c,d; scanf("%s",a); printf("a ...
C語言 gets()和puts()函數. 瀏覽人數: 1.6K最近更新: 2020年10月13日. gets() 函數從用戶讀取字符串, puts() 函數打印字符串。這兩個函數都在 <stdio.h> 頭文件中 ...
#10. C 库函数– puts() | 菜鸟教程
C 库函数- puts() C 标准库- <stdio.h> 描述C 库函数int puts(const char *str) 把一个字符串写入到标准输出stdout,直到空字符,但不包括空字符。
#11. 說明C語言puts & printf函式的差異
#12. C 語言標準函數庫分類導覽- stdio.h puts()
C 語言 標準函數庫分類導覽- stdio.h puts(). stdio.h 的函數puts() 將字串傳送到標準輸出裝置。 以下程式示範使用puts() 的結果 #include <stdio.h> int main(void) ...
#13. C语言gets()和puts()函数 - 易百教程
C语言 gets()和puts()函数. gets() 函数从用户读取字符串, puts() 函数打印字符串。这两个函数都在 <stdio.h> 头文件中定义。 下面来看看一个简单使用 gets() 和 ...
#14. 字元陣列
在C語言中沒有專門的字串變數,通常用一個字元陣列來存放一個字串。 ... 從程式中可以看出puts函數中可以使用轉義字元,因此輸出結果成為兩行。puts函數完全可以 ...
#15. C语言输入和输出(getchar() 、 putchar() 、gets() 、 puts()
C语言 中 stdio.h 头文件中内置输入输出函数,如下列出:. printf 与 scanf; getchar 与 putchar; gets 与 puts. 输入 ...
#16. C語言中put()與puts()的區別是什麼? - 劇多
但是而這都是對於字元陣列或者指向字元陣列的指標進行操作的。 比如你在char str[20];裡面輸入了一個字串(可以用gets(str)函式實現)。然後透過puts ...
#17. C语言puts()函数:把一个字符串写入到标准输出stdout
C语言puts ()函数:把一个字符串写入到标准输出stdout函数名:puts头文件:<stdio.h>函数原型:intputs(char*str);功能:把一个字符串写入到标准输出stdout,直到空字符, ...
#18. stdio.h - 維基百科,自由的百科全書
C語言 中的所有輸入和輸出都由抽象的字節流來完成,對文件的訪問也通過關聯的輸入或輸出流進行。這一模式隨UNIX作業系統而普及,在現代作業系統和程式語言中仍被廣泛 ...
#19. 30天學會C語言: Day 22-陣列處理 - iT 邦幫忙
C 語言 沒辦法透過比較運算檢查兩個陣列(或字串)是否相等,必須要透過陣列對每個圓一個一個做 ... if(correct==1) puts("Succeed"); else puts("Failed") return 0; }.
#20. C 語言筆記— putchar() getchar() gets() puts() - Sharon Peng
當我想要輸入一大串文字或是但一輸入位元卻不想打那麼多字時,gets(), puts(), getchar(), putchar().會是一個非常好的選項。. “C 語言筆記 — putchar() getchar() ...
#21. C语言gets和puts函数- C语言基础教程实例源码 - 编程字典
C语言 gets和puts函数```c /* getsputs.c -- using gets() and puts() */ #include #define STLEN 81 int main(void) { char words[STLEN]; puts("Enter a string, ...
#22. C語言介紹
(5)C語言程式需經由編譯器依下列步驟才能得到最後的可執行檔. ... 基本上gets(),puts()與getchar(),putchar()的功能類似,差異僅在於前者以字串當作輸入/輸出,而 ...
#23. c語言puts 輸出陣列出現亂碼 - 知識的邊界
c語言puts 輸出陣列出現亂碼,1樓匿名使用者因為沒有結束標識0 include include void copy char char int int main v.
#24. C語言程式中gets和puts是什麼意思 - Howcando問答
gets和scanf的思意有點像,可以是SCANF的變象,它們都是接收從鍵盤上輸入的內容,只是二者之間接收資料時的規則不同,gets是用來專門接收輸入的字串 ...
#25. 輸出入函數
輸出(output):是指執行時將資料列印在螢幕上,包括cout、printf()、puts()、putchar()等。 ... 而C++語言的變數位址表示方式就是在前面加上『&』,所以要特別留心。
#26. puts 与printf - C 语言笔记
C. C 语言笔记. puts 与printf. puts 是output string 的缩写,只能用来输出字符串,不能输出整数、小数、字符等,我们需要用另外一个函数,那就是printf。
#27. 第3章基本輸出函式及輸入函式
C語言 對於資料輸入與資料輸出處理,是. 藉由呼叫函式(function)來達成 ... 注意]在程式中,只要使用到C語言的庫存函式,則必 ... puts()函式:只能輸出字串.
#28. puts和printf函數區別_BelleDiao的博客-程序員宅基地
《C語言》中,puts和printf函數區別_BelleDiao的博客-程序員宅基地 ... puts()函數只用來輸出字符串,沒有格式控制,裡面的參數可以直接是字符串或者 ...
#29. c语言puts("")啥意思? - 百度知道
puts 是输出参数字符串后换行这里字符串为空,所以只会输出换和putchar('\n')效果是一样的. 已赞过 已踩过<. 你对这个回答的评价是? 评论 收起.
#30. C 速查手冊- 11.6.4 puts() - 程式語言教學誌
#include <stdio.h> int main(void) { char *c1 = "Hello"; puts(c1); return 0; } /* 《程式語言教學誌》的範例程式http://kaiching.org/ 檔名:cputs.c 功能: ...
#31. C语言puts和printf函数的区别 - 51CTO博客
C语言puts 和printf函数的区别,两者的区别在于puts的功能更单一,只能输出字符串,而printf可以根据给定的格式输出多种类型的数据。1、puts()函数用来 ...
#32. 你所不知道的C語言:指標篇 - HackMD
這個數值其實是執行檔反組譯後的函式進入點,但這數值並非存在實體記憶體的實際數值,而是虛擬記憶載入位址。 由於 puts 的function prototype 是 int puts(const char *s) ...
#33. C語言教學01-Hello world!
做為認識程式語言的開始。 #include <stdio.h> int main() { puts("Hello world!"); return 0; }. 運行結果. Hello world!
#34. try-except 陳述式(C)
Microsoft C/c + + 會使用try-except 語句語言延伸模組,來實行(SEH) 的結構 ... __finally { puts("in finally"); } } __except( puts("in filter"), ...
#35. 我用gets 輸入字串,再由puts 輸出至螢幕 - 大家來玩C 語言
#include <stdio.h> int main(void) { char a[100]; printf("請輸入一字串: "); gets(a); printf("您剛輸入的字串= "); puts(a); return 0; }
#36. C語言- 簡單小考題(2) - C強制轉型 - Rookie worker
C語言 - 簡單小考題(2) - C強制轉型. What's the output in this function? void foo(void) { unsigned int a = 6; int b = -20; (a+b > 6) ? puts("> ...
#37. c 字串輸入
Putchar、Getchar、Puts、Fgets · 資料型態· Printf 與Scanf ... 回C 語言目錄. putchar、getchar、puts、fgets. ... 在C 語言中使用getchar 函式讀取字串輸入.
#38. 請問c語言中的t在c中怎麼表示翱
\t表示一個製表符,可以按鍵盤上的tab鍵獲得輸出中,c語言使用printf 函式進行輸出,也可以使用putchar列印一個字元,或使用puts函式輸出,格式如下:.
#39. 第三章基本輸出與輸入的方法本章簡介 - 林偉川
在C 語言函式庫中有不少輸出/ 入相關函式, ... putchar():函數名稱取put 以及character 兩. 個字組成。如名稱所示, ... %c %c 會輸出字元型別的變數nx= 'x', 因.
#40. 用C語言編譯成組合語言的putf和printf有什麼區別 - 程式人生
這是我的c程式,使用 puts() : #include <stdio.h> int main(void){ puts("testing"); } 在使用 gcc -S -o sample.s sample.c 將其編譯成程式集 ...
#41. 14.1 字串常值(String Literals)
在C語言中,這種用雙引號框起來的字串又稱為字串常值(string literals),我們已經在printf()函式中時常 ... 在C語言裡,你可以使用printf()與puts()來進行字串的輸出。
#42. c語言求教puts和printf的區別 - 好問答網
c語言 求教puts和printf的區別,1樓吉祥二進位制兩者的區別在於puts的功能更單一,只能輸出字串,而printf可以根據給定的格式輸出多種型別的資料。
#43. C語言中怎麼用陣列定義漢字? - 優幫助
1 首先,開啟c語言編譯器,新建一個初始cpp檔案,例如test cpp。 ... 2、在test.cpp檔案中,輸入c語言**:char a[20] = "我你";puts(a);。
#44. Linux C語言字符串輸出函數puts()、fputs()、printf() 詳解
Linux C語言字符串輸出函數puts()、fputs()、printf() 詳解 ... puts()函數用來向標準輸出設備(屏幕)寫字符串並換行,調用格式為:. puts(s);.
#45. C語言輸入輸出字串 - 輕鬆奔跑
puts ()和gets()都是陣列函式,輸入或輸出前要定義陣列. 例如:. char a[50];. gets(a);. puts(a);. 就是一個簡單的輸入後再將輸入的東西輸出 ...
#46. 劉和師
注意:C語言每一道敘述後面都要加上一個分號";"當結尾,不可遺漏。 ... puts()比printf()簡單,但只能輸出一個字串,. 無法包含變數,若只是要單純的 ...
#47. C/C++ scanf和gets 區別, printf和puts區別 - 开发者知识库
ref 1. scanf和gets區別| 博客園2. printf和puts區別| CSDN scanf和gets都能從輸入流stdin讀取字符串,那么它們有什么區別呢? sca.
#48. 急!C語言程式設計,刪除從鍵盤輸入的字串中的小寫字母
2、接下來,我們開始書寫我們的**,首先我們知道,對於標準輸入輸出來說,c提供了兩個輸出字串的函式,printf中用%s來控制輸出,還有一個是puts來輸出,這 ...
#49. C語言-輸入輸出特殊(字符串) - 人人焦點
格式:int puts(const char *s)功能:向顯示器輸出字符串(輸出完,自動換行)說明: ... 衆所周知,C語言需要輸入和輸出,那麼今天小編我就帶領大家去學習這個編程必 ...
#50. C語言- 第十五章| 字串- 比較、搜尋 - J.J.'s Blogs
printf("請輸入密碼:"); fgets(input, sizeof(input) / sizeof(input[0]), stdin); if (strcmp(passwd, input) == 0) { puts("密碼正確"); }
#51. c/c++學習系列之putchar、getchar、puts、gets的運用 - 台部落
c/c++學習系列之putchar、getchar、puts、gets的運用如果您只想取得使用者 ... #include <stdio.h> int main(void) { char c; printf("請輸入一個字 ...
#52. C語言程式設計(第二版)(附範例光碟)
C 語言 常用的輸入輸出函數. 輸入函數. 輸出函數. 標頭檔 scanf( ) getchar( ) gets( ) printf( ) putchar( ) puts() stdio.h getch( ) getche( ) putch() conio.h.
#53. c語言get的用法 - Cnap
c語言puts 和gets的用法2020-03-31在語言使用過程中我們常常用到庫函數,這些庫函數是又人事先編譯好的,那么它們內部的代碼究竟如何?我們可以根據各函數的說明及其 ...
#54. (教學) 10小時學C 語言 - Scribd
puts scanf vprintf │ ecvt,fprintf,putc, ... 在C 語言,沒有運算符號可以表示次方,但是C 語言有提供次方的函式: pow(), pow( 2 , 3 ) 表示2 的3 次方。
#55. 黑馬程式設計師-C語言-字符串 - 壹讀
puts 在輸出字符串時會自動換行,但與printf不同的是,puts不能進行格式化輸出。gets相對於scanf來說好處在於gets函數並不以空格為字符串結束輸入的標誌, ...
#56. c語言:如何將字串中指定的字元替換為另一個指定字元
2、在test.cpp檔案中,輸入C語言程式碼:. char a[] = "hello world!", b[20];. strrpl(a, b, sizeof(b), "world", "c");. puts(a);.
#57. C語言實現輸入密碼顯示星號實現教程 - 程式師世界
日期:2017/1/13 17:31:57 編輯:關於C語言. 眾所周知,一個良好的密碼輸入程序是在用戶輸入密碼 ... char c;. int i = 0; puts(prompt); while ((c=getch()) != '\r')
#58. 24 x 陣列所能儲存的int 數量:6 C 語言入門經典
C 語言 入門經典. |09 陣列. C 語言入門經典. 使用一維陣列>> ... |gets() 與puts() 函數. 與scanf() 類似,讀取使用者輸入的字串。 c 是一個特定長度的字元陣列變數 ...
#59. C語言: switch 條件判斷
C語言 : switch 條件判斷 ... break; case 7: puts("得C"); break; case 6: puts("得D"); break; default: puts("得E(不及格)"); } return 0; }
#60. C語言-檔案I/O | 鋼彈盪單槓
這篇是講關於C語言如何操作檔案. ... puts("來源檔案開啟失敗"); return 1; } FILE *file2 = fopen("target.txt", "w"); if(!file2) {
#61. 零基础学编程C语言-get puts函数补充-网易公开课
get puts函数补充本课程为老九学堂推出的零基础学编程系列之C语言,老九学堂是专注于大学生IT就业的学习社群,汇聚无数大咖与优质课程,将以游戏化的方式教学, ...
#62. C語言putsp2裡的p2是什麼意思 - 第一問答網
C語言 putsp2裡的p2是什麼意思,1樓陳釗翔如果p是普通變數就在數學值上加2如果p是地址就向前偏移兩個單位地址c語言中p12什麼意思?具體怎麼表示的?2.
#63. 如何在C 語言中把整數轉換為字元 - Delft Stack
本教程介紹了在C 語言中把整數轉換為字元的不同方法。 ... ++anyNumber) { charValue[11] = anyNumber+ '0'; puts(charValue); } return 0; }.
#64. 字串拷貝函式strcpy(), strncpy(), strchr(), strstr()函式用法特點| IT人
C語言 ——常用標準輸入輸出函式scanf(), printf(), gets(), puts(), getchar(), ... printf() , 輸出字串,可以指定格式(%d, %s, %c等等);.
#65. C++的輸出入cin/cout和scanf/printf誰比較快? | Chino's
有打過資訊競賽的人,一定有遇過用cin/cout結果TLE,換成scanf/printf就AC的情況。難道cin/cout真的比較慢嗎?為什麼C++要做出一個比C還要更慢的輸入 ...
#66. C/C++ 語言新手十三誡-- Ver. 2016 - 看板C_and_CPP - 批踢踢 ...
但不能取代完整的學習,請自己好好研讀一兩本C 語言的好書, ... length); } else { // name == NULL puts("輸入值太大或系統已無足夠空間"); } ...
#67. 第九章陣列與字串
在C 語言中,陣列使用前必須先宣告:. ○ 在C 語言中,陣列使用前必須先宣告: ... C語言不會自動檢查陣列界限(可增快執行速度) ... gets() 與puts() 的使用範例:.
#68. 明解C語言·入門篇(第3版) - 博客來
書名:明解C語言·入門篇(第3版),語言:簡體中文,ISBN:9787115404824,頁數:405,出版社:人民郵電出版社,作者:(日)柴田望洋,出版日期:2015/11/01, ...
#69. C 程式設計
C語言 簡介; 基本資料型態, 變數, 基本輸入輸出; 控制敘述- 選擇控制與重覆控制 ... puts( ). 從指定的檔案中讀取一整行字串(包含換行字元)或讀取指定長度的字串。
#70. C語言--putchar()、getchar()函數用法- 碼上快樂
單個字符輸入輸出函數putchar函數單個字符輸出函數例nbsp nbsp nbsp putchar函數的格式和使用方法。 include stdio.h void main char ch N , ch E ...
#71. C語言常用字串處理函式 - w3c學習教程
C語言 常用字串處理函式,下面介紹幾個最常用的字串函式。 字串輸出函式puts 格式puts 字元陣列名功能把字元陣列中的字串輸出到顯示器。
#72. 為什麼C語言的Hello,world都是用printf輸出而不是puts? - GetIt01
幾乎所有C語言教材的hello,world都是用printf輸出的。我覺得printf是「格式化輸出」,僅僅輸出一個字元串是大材小用了,而且還要手動加.
#73. C語言| 字符陣列 - 有解無憂
C語言 對字符陣列初始化,最容易理解的方式是用“初始化串列”,把各個字符依次賦給陣列中各 ... 3、注意:用gets和puts函式只能輸出或輸入一個字串,.
#74. C語言入門20090521 上課小記
做字串輸出, 一個puts 一定是一行 puts("Enter your name:"); //等待使用者輸入 gets(name); //列印出剛剛的name 好處是空格也可以抓進來
#75. C語言新手發問- Delphi K.Top 討論區
不好意思 因為剛學C語言所以我問的問題可能有點簡單這是考試的題目下禮拜會解答可是我等不及所以我想趕快知道答案 ... puts("---The Result is---");
#76. C語言進階– 字串處理 - 翔超科技
C語言 進階– 字串處理 ... puts ( "your name is " );. puts (name); ... putchar (c) is the same as putc (c, stdout) part of the standard C-rtl, ...
#77. 《明解C語言》第一章學習筆記_osc_q5m9dzk0
顯示讀取到的三個整數的和*/ #include<stdio.h> int main(void) { int a,b,c; puts("請輸入三個整數"); printf("整數1:");scanf("%d",&a); ...
#78. 《學會C 語言》學習筆記(五):基本輸入與輸出 - Memo's Blog
printf() 函數輸出整數,格式字元是 %s 。 使用 gets() 函數讀取輸入字串(可以讀取空白字元),使用 puts 函數將字串 ...
#79. c語言二位陣列a1表示什麼C語言二位陣列a1表示什麼30 - 多學網
關於puts函式:. int puts(char *string);. 其傳入的引數是char*型的,也就是字串。 我不知道你 ...
#80. 輸入某月份的整數值1 12,輸出該月份的英文名稱c語言 - 嘟油儂
輸入某月份的整數值1 12,輸出該月份的英文名稱c語言,感覺對,但提交不對,求幫助,1樓香夢沉酣將你的輸出 ... puts(months[m-1]); 是輸出字元不是字串.
#81. puts:功能,用法,程式例,說明 - 中文百科全書
puts ()函式用來向標準輸出設備(螢幕)輸出字元串並換行,具體為:把字元串輸出 ... TCL語言檔案句柄編輯channelID可以理解為c的檔案句柄,varName如果定義,輸入值就賦.
#82. c語言結構體處理10個學生的成績
㈠ c語言程序:某班有10名同學,建立學生結構體類型,包括學號,姓名,3門課程的成績,編寫程序. #include <stdio.h>. #include"string.h".
#83. 深入淺出C語言
Why C? #include <stdio.h> int main() { puts("C Rocks!"); return 0; } 9 > gcc rocks.c ...
#84. C語言的gets, puts - Herr Deng桑的教學blog
C語言 的gets, puts. C語言的gets, ... int puts( char *str );它在輸出字串後,會直接進行換行 ... 使用gets遇到空白後面的字會忽略使用puts則不會.
#85. C 語言測試: 指標 - 小狐狸事務所
使用指標處理字串較方便. 事實上, 許多C 語言內建函式內部都是使用指標實作的. 以下的測試程式我參考了下列 ...
#86. burden中文(繁體)翻譯:劍橋詞典
noun [ C ]. uk. Your browser doesn't support HTML5 audio ... The war had put an insupportable financial burden on the country.
#87. C語言中的printf()和puts()有什麼區別? - Siwib
我知道您可以使用printf()和puts()進行打印。我還可以看到printf()允許您插值變量並進行格式化。 puts()僅僅是printf()的原始版本。應該是我們...
#88. Learn C Programming Language Tutorial - javatpoint
C Programming Index · String in C · C gets() & puts() · C String Functions · C strlen() · C strcpy() · C strcat() · C strcmp() · C strrev() ...
#89. Stigmata by Phyllis Alesia Perry (July 22,1998): 圖書 - Amazon ...
What put her in there for a little over 10 years was what the doctor's ... mais si cela à été possible c'est aussi parce que le livre était dans un état ...
#90. Express Scripts Members: Manage Your Prescriptions Online
Millions trust Express Scripts for safety, care and convenience. Express Scripts makes the use of prescription drugs safer and more affordable.
#91. C語言取得質數問題 - 單純的每一天
在網路上看到有人問,無聊就寫了一下,順便回憶一下C語言XD. 題目是這樣:. 輸入兩個正整數,兩數都要比1大,且都要小於100000000,兩數相減小於1000.
#92. Try Google Input Tools online
English; English - DVORAK; English. Show Keyboard; Hide Keyboard. English. ` 1 2 3 4 5 6 7 8 9 0 - = q w e r t y u i o p [ ] \. a s d f g h j k l ; '. z x c
#93. Mercy | Doctors, Hospitals & Clinics in MO, AR, OK & KS
Rebounding Flu Cases Puts Health Experts on Alert · Cropped hand of girl washing hands at bathroom sink ... 注意:如果您講中文,可免費為您提供語言援助服務。
#94. HealthEquity - Industry's #1 HSA Administrator
HealthEquity and WageWorks empower Americans to connect health and wealth by providing health savings accounts (HSAs) and offering a true total solution for ...
#95. Google Translate
Google's free service instantly translates words, phrases, and web pages between English and over 100 other languages.
#96. 我應該使用printf(“ \ n”)還是putchar('\ n')在C語言中打印 ...
我應該使用printf(“ \ n”)還是putchar('\ n')在C語言中打印換行符嗎? ... 參數(只是一個沒有格式說明符的格式字符串)調用 puts 相反(假設它以換行符結尾)。
#97. C語言基礎必修課(涵蓋「APCS大學程式設計先修檢測」試題詳解)(電子書)
4.5.2 puts()字串輸出函式 puts()函式是用來將指定的字串顯示在螢幕目前的插入點游標上,它會將 puts()函式小括號內的字元陣列或字串常數顯示到螢幕上。
#98. C語言程式設計概論與實踐 - 第 94 頁 - Google 圖書結果
這是由於在 C 語言中規定,陣列名稱就代表了該陣列的首位址。 ... (1)字串輸出函數 puts 格式 puts (字元陣列名稱)功能:把字元陣列中的字串輸出到顯示器。
c語言puts 在 C/C++ 語言新手十三誡-- Ver. 2016 - 看板C_and_CPP - 批踢踢 ... 的推薦與評價
C/C++ 語言新手十三誡(The Thirteen Commandments for Newbie C/C++ Programmers)
by Khoguan Phuann
請注意:
(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
並多多實作練習。
(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
解答了。
(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
那只是不足為恃的一時僥倖。
(4) 不守十三誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。
=============================================================
目錄: (頁碼/行號) 2/24
01. 不可以使用尚未給予適當初值的變數 3/46
02. 不能存取超過陣列既定範圍的空間 5/90
03. 不可以提取不知指向何方的指標 7/134
04. 不要試圖用 char* 去更改一個"字串常數" 12/244
05. 不能在函式中回傳一個指向區域性自動變數的指標 16/332
06. 不可以只做 malloc(), 而不做相應的 free() 19/398
07. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值 21/442
08. ++i/i++/--i/i--/f(&i)哪個先執行跟順序有關 24/508
09. 慎用Macro 27/574
10. 不要在 stack 設置過大的變數以避免堆疊溢位(stack overflow) 32/684
11. 使用浮點數精確度造成的誤差問題 35/750
12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞 36/772
13. 函式內 new 出來的空間記得要讓主程式的指標接住 40/860
直接輸入數字可跳至該頁碼
或用:指令輸入行號
01. 你不可以使用尚未給予適當初值的變數
錯誤例子:
int accumulate(int max) /* 從 1 累加到 max,傳回結果 */
{
int sum; /* 未給予初值的區域變數,其內容值是垃圾 */
for (int num = 1; num <= max; num++) { sum += num; }
return sum;
}
正確例子:
int accumulate(int max)
{
int sum = 0; /* 正確的賦予適當的初值 */
for (int num = 1; num <= max; num++) { sum += num; }
return sum;
}
備註:
根據 C Standard,具有靜態儲存期(static storage duration)的變數,
例如 全域變數(global variable)或帶有 static 修飾符者等,
如果沒有顯式初始化的話,根據不同的資料型態予以進行以下初始化:
若變數為算術型別 (int , double , ...) 時,初始化為零或正零。
若變數為指標型別 (int*, double*, ...) 時,初始化為 null 指標。
若變數為複合型別 (struct, double _Complex, ...) 時,遞迴初始化所有成員。
若變數為聯合型別 (union) 時,只有其中的第一個成員會被遞迴初始化。
(以上感謝Hazukashiine板友指正)
(但是有些MCU 編譯器可能不理會這個規定,所以還是請養成設定初值的好習慣)
補充資料:
- 精華區z->5->1->1->1
- C11 Standard 5.1.2, 6.2.4, 6.7.9
02. 你不可以存取超過陣列既定範圍的空間
錯誤例子:
int str[5];
for (int i = 0 ; i <= 5 ; i++) str[i] = i;
正確例子:
int str[5];
for (int i = 0; i < 5; i++) str[i] = i;
說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1
C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,
將導致無法預期的後果。
備註:
C++11之後可以用Range-based for loop提取array、
vector(或是其他有提供正確.begin()和.end()的class)內的元素
可以確保提取的元素一定落在正確範圍內。
例:
// vector
std::vector<int> v = {0, 1, 2, 3, 4, 5};
for(const int &i : v) // access by const reference
std::cout << i << ' ';
std::cout << '\n';
// array
int a[] = {0, 1, 2, 3, 4, 5};
for(int n: a) // the initializer may be an array
std::cout << n << ' ';
std::cout << '\n';
補充資料:
https://en.cppreference.com/w/cpp/language/range-for
03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。
錯誤例子:
char *pc1; /* 未給予初值,不知指向何方 */
char *pc2 = NULL; /* pc2 起始化為 null pointer */
*pc1 = 'a'; /* 將 'a' 寫到不知何方,錯誤 */
*pc2 = 'b'; /* 將 'b' 寫到「位址0」,錯誤 */
正確例子:
char c; /* c 的內容尚未起始化 */
char *pc1 = &c; /* pc1 指向字元變數 c */
*pc1 = 'a'; /* c 的內容變為 'a' */
/* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
char *pc2 = (char *) malloc(10);
pc2[0] = 'b'; /* 動態配置來的第 0 個字元,內容變為 'b'
free(pc2);
說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。
( 使用者記得要檢查 malloc 回傳是否為 NULL,
礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )
錯誤例子:
char *name; /* name 尚未指向有效的空間 */
printf("Your name, please: ");
fgets(name,20,stdin); /* 您確定要寫入的那塊空間合法嗎??? */
printf("Hello, %s\n", name);
正確例子:
/* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */
char name[21]; /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
printf("Your name, please: ");
fgets(name,20,stdin);
printf("Hello, %s\n", name);
正確例子(2):
若是在執行時期才能決定字串的最大空間,C提供兩種作法:
a. 利用 malloc() 函式來動態分配空間,用malloc宣告的陣列會被存在heap
須注意:若是宣告較大陣列,要確認malloc的回傳值是否為NULL
size_t length;
printf("請輸入字串的最大長度(含null字元): ");
scanf("%u", &length);
name = (char *)malloc(length);
if (name) { // name != NULL
printf("您輸入的是 %u\n", length);
} else { // name == NULL
puts("輸入值太大或系統已無足夠空間");
}
/* 最後記得 free() 掉 malloc() 所分配的空間 */
free(name);
name = NULL; //(註1)
b. C99開始可使用variable-length array (VLA)
須注意:
- 因為VLA是被存放在stack裡,使用前要確認array size不能太大
- 不是每個compiler都支援VLA(註2)
- C++ Standard不支援(雖然有些compiler支援)
float read_and_process(int n)
{
float vals[n];
for (int i = 0; i < n; i++)
vals[i] = read_val();
return process(vals, n);
}
正確例子(3):
C++的使用者也有兩種作法:
a. std::vector (不管你的陣列大小會不會變都可用)
std::vector<int> v1;
v1.resize(10); // 重新設定vector size
b. C++11以後,若是確定陣列大小不會變,可以用std::array
須注意:一般使用下(存在stack)一樣要確認array size不能太大
std::array<int, 5> a = { 1, 2, 3 }; // a[0]~a[2] = 1,2,3; a[3]之後為0;
a[a.size() - 1] = 5; // a[4] = 0;
備註:
註1. C++的使用者,C++03或之前請用0代替NULL,C++11開始請改用nullptr
註2. gcc和clang支援VLA,Visual C++不支援
補充資料:
https://www.cplusplus.com/reference/vector/vector/resize/
04. 你不可以試圖用 char* 去更改一個"字串常數"
試圖去更改字串常數(string literal)的結果會是undefined behavior。
錯誤例子:
char* pc = "john"; /* pc 現在指著一個字串常數 */
*pc = 'J'; /* undefined behaviour,結果無法預測*/
pc = "jane"; /* 合法,pc指到在別的位址的另一個字串常數*/
/* 但是"john"這個字串還是存在原來的地方不會消失*/
因為char* pc = "john"這個動作會新增一個內含元素為"john\0"的static char[5],
然後pc會指向這個static char的位址(通常是唯讀)。
若是試圖存取這個static char[],Standard並沒有定義結果為何。
pc = "jane" 這個動作會把 pc 指到另一個沒在用的位址然後新增一個
內含元素為"jane\0"的static char[5]。
可是之前那個字串 "john\n" 還是留在原地沒有消失。
通常編譯器的作法是把字串常數放在一塊read only(.rdata)的區域內,
此區域大小是有限的,所以如果你重複把pc指給不同的字串常數,
是有可能會出問題的。
正確例子:
char pc[] = "john"; /* pc 現在是個合法的陣列,裡面住著字串 john */
/* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
pc[3]='n', pc[4]='\0' */
*pc = 'J';
pc[2] = 'H';
說明:字串常數的內容應該要是"唯讀"的。您有使用權,但是沒有更改的權利。
若您希望使用可以更改的字串,那您應該將其放在合法空間
錯誤例子:
char *s1 = "Hello, ";
char *s2 = "world!";
/* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
造成寫入到程式無權碰觸的記憶體空間 */
strcat(s1, s2);
正確例子(2):
/* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
char s1[20] = "Hello, ";
char *s2 = "world!";
/* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
strcat(s1, s2);
C++對於字串常數的嚴格定義為const char* 或 const char[]。
但是由於要相容C,char* 也是允許的寫法(不建議就是)。
不過,在C++試圖更改字串常數(要先const_cast)一樣是undefined behavior。
const char* pc = "Hello";
char* p = const_cast<char*>(pc);
p[0] = 'M'; // undefined behaviour
備註:
由於不加const容易造成混淆,
建議不管是C還是C++一律用 const char* 定義字串常數。
補充資料:
https://en.cppreference.com/w/c/language/string_literal
https://en.cppreference.com/w/cpp/language/string_literal
字串函數相關:#1IOXeMHX
undefined behavior : 精華區 z -> 3 -> 3 -> 23
05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
[感謝 gocpp 網友提供程式例子]
錯誤例子:
char *getstr(char *name)
{
char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
strcat(buf, name);
return buf;
}
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
正確例子:
void getstr(char buf[], int buflen, char const *name)
{
char const s[] = "hello, ";
strcpy(buf, s);
strcat(buf, name);
}
正確例子:
int* foo()
{
int* pInteger = (int*) malloc( 10*sizeof(int) );
return pInteger;
}
int main()
{
int* pFromfoo = foo();
}
說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack,
又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
(但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成
memory leak)
[針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]
正確例子:
#include <string> /* 並非 #include <cstring> */
using std::string;
string getstr(string const &name)
{
return string("hello, ") += name;
}
06. [C]你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失
但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
以再被 free(),也不可以提取(dereference)這個指標。
小技巧: 可在 free 之後將指標指到 NULL,free不會對空指標作用。
例:
int *p = malloc(sizeof(int));
free(p);
p = NULL;
free(p); // free不會對空指標有作用
[C++] 你不可以只做 new, 而不做相應的 delete (除了unique_ptr以外)
註:new 與 delete 對應,new[] 與 delete[] 對應,
不可與malloc/free混用(結果不可預測)
切記,做了幾次 new,就必須做幾次 delete
小技巧: 可在 delete 之後將指標指到0或nullptr(C++11開始),
由於 delete 本身會先做檢查,因此可以避免掉多次 delete 的錯誤
正確例子:
int *ptr = new int(99);
delete ptr;
ptr = nullptr;
delete ptr; /* delete 只會處理指向非 NULL 的指標 */
備註:
C++11後新增智能指標(smart pointer): unique_ptr
當unique_ptr所指物件消失時,會自動釋放其記憶體,不需要delete。
例:
#include <memory> // 含unique_ptr的標頭檔
std::unique_ptr<int> p1(new int(5));
補充資料:
https://en.cppreference.com/w/cpp/memory/unique_ptr
07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
的結果,其範圍是否會超出變數的型別
錯誤例子:
unsigned int sum = 2000000000 + 2000000000; /* 超出 int 存放範圍 */
unsigned int sum = (unsigned int) (2000000000 + 2000000000);
double f = 10 / 3;
正確例子:
/* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
unsigned int sum = 2000000000u + 2000000000u;
/* 或是用顯式的轉型 */
unsigned int sum = (unsigned int) 2000000000 + 2000000000;
double f = 10.0 / 3.0;
錯誤例子:
unsigned int a = 0;
int b[10];
for(int i = 9 ; i >= a ; i--) { b[i] = 0; }
說明:由於 int 與 unsigned 共同運算的時候,會轉換 int 為 unsigned,
因此迴圈條件永遠滿足,與預期行為不符
錯誤例子: (感謝 sekya 網友提供)
unsigned char a = 0x80; /* no problem */
char b = 0x80; /* implementation-defined result */
if( b == 0x80 ) { /* 不一定恒真 */
printf( "b ok\n" );
}
說明:語言並未規定 char 天生為 unsigned 或 signed,因此將 0x80 放入
char 型態的變數,將會視各家編譯器不同作法而有不同結果
錯誤例子(以下假設為在32bit機器上執行):
#include <math.h>
long a = -2147483648 ; // 2147483648 = 2 的 31 次方
while (labs(a)>0){ // labs(-2147483648)<0 有可能發生
++a;
}
說明:如果你去看C99/C11 Standard,你會發現long
變數的最大/最小值為(被define在limits.h)
LONG_MIN -2147483647 // compiler實作時最小值不可大於 -(2147483648-1)
LONG_MAX 2147483647 // compiler實作時最小值不可小於 (2147483648-1)
不過由於32bit能顯示的範圍就是2**32種,所以一般16/32bit作業系統會把
LONG_MIN多減去1,也就是int 的顯示範圍為(-LONG_MAX - 1) ~ LONG_MAX。
(64bit的作業系統long多為8 bytes,但是依舊符合Standard要求的最小範圍)
當程式跑到labs(-2147483648)>0時,由於2147483648大於LONG_MAX,
Standard告訴我們,當labs的結果無法被long有限的範圍表示,
編譯器會怎麼幹就看他高興(undefined behavior)。
(不只long,其他如int、long long等以此類推)
補充資料:
- C11 Standard 5.2.4.2.1, 7.22.6.1
- https://www.fefe.de/intof.html
08. ++i/i++/--i/i--/f(&i)哪個先執行跟順序有關
++i/i++ 和--i/i-- 的問題幾乎每個月都會出現,所以特別強調。
當一段程式碼中,某個變數的值用某種方式被改變一次以上,
例如 ++x/--x/x++/x--/function(&x)(能改變x的函式)
- 如果Standard沒有特別去定義某段敘述中哪個部份必須被先執行,
那結果會是undefined behavior(結果未知)。
- 如果Standard有特別去定義執行順序,那結果就根據執行順序決定。
C/C++均正確的例子:
if (--a || ++a) {} // ||左邊先計算,如果左邊為1右邊就不會算
if (++i && f(&i)) {} // &&左邊先計算,如果左邊為0右邊就不會算
a = (*p++) ? (*p++) : 0 ; // 問號左邊先計算
int j = (++i, i++); // 這裡的逗號為運算子,表示依序計算
C/C++均錯誤的例子:
int j = ++i + i++; // undefined behavior,Standard沒定義+號哪邊先執行
x = x++; // undefined behavior, Standard沒定義=號哪邊先執行
printf( "%d %d %d", I++, f(&I), I++ ); // undefined behavior, 原因同上
foo(i++, i++); // undefined behavior,這裡的逗號是用來分隔引入參數的
// 分隔符(separator)而非運算子,Standard沒定義哪邊先執行
在C與C++03錯誤但是在C++11開始(但不包括C)正確的例子:
C++11中,++i/--i為左值(lvalue),i++/i--為右值(rvalue)。
左值可以被assign value給它,右值則不行。
而在C中,++i/--i/i++/i--都是右值。
所以以下的code在C++會正確,C則否。
++++++++++phew ; // C++11會把它解釋為++(++(++(++(++phew))));
i = v[++i]; // ++i會先完成
i = ++i + 1; // ++i會先完成
在C++17開始(但不包括C)才正確的例子:
cout << i << i++; // 先左後右
a[i] = i++; // i++先做
a[x++] = --x; // 先處理--x,再處理a[x++] (loveflames補充)
補充資料
- Undefined behavior and sequence points
https://stackoverflow.com/questions/4176328/undefined-behavior-and-
sequence-points)
- C11 Standard 6.5.13-17,Annex C
- Sequence poit
https://en.wikipedia.org/wiki/Sequence_point
- Order of evaluation
https://en.cppreference.com/w/cpp/language/eval_order
09. 慎用macro(#define)
Macro是個像鐵鎚一樣好用又危險的工具:
用得好可以釘釘子,用不好可以把釘子打彎、敲到你手指或被抓去吃子彈。
因為macro 定義出的「偽函式」有以下缺點:
(1) debug會變得複雜。
(2) 無法遞迴呼叫。
(3) 無法用 & 加在 macro name 之前,取得函式位址。
(4) 沒有namespace。
(5) 可能會導致奇怪的side effect或其他無法預測的問題。
所以,使用macro前,請先確認以上的缺點是否會影響你的程式運行。
替代方案:enum(定義整數),const T(定義常數),inline function(定義函式)
C++的template(定義可用不同type參數的函式),
或C++11開始的匿名函式(Lambda function)與constexpr T(編譯期常數)
以下就針對macro的缺點做說明:
(1) debug會變得複雜。
編譯器不能對macro本身做語法檢查,只能檢查預處理(preprocess)後的結果。
(2) 無法遞迴呼叫。
根據C standard 6.10.3.4,
如果某macro的定義裡裏面含有跟此macro名稱同樣的的字串,
該字串將不會被預處理。
所以:
#define pr(n) ((n==1)? 1 : pr(n-1))
cout<< pr(5) <<endl;
預處理過後會變成:
cout<< ((5==1)? 1 : pr(5 -1)) <<endl; // pr沒有定義,編譯會出錯
(3) 無法用 & 加在 macro name 之前,取得函式位址。
因為他不是函式,所以你也不可以把函式指標套用在macro上。
(4) 沒有namespace。
錯誤例子:
#define begin() x = 0
for (std::vector<int>::iterator it = myvector.begin();
it != myvector.end(); ++it) // begin是std的保留字
std::cout << ' ' << *it;
改善方法:macro名稱一律用大寫,如BEGIN()
(5) 可能會導致奇怪的side effect或其他無法預測的問題。
錯誤例子:
#include <stdio.h>
#define SQUARE(x) (x * x)
int main()
{
printf("%d\n", SQUARE(10-5)); // 預處理後變成SQUARE(10-5*10-5)
return 0;
}
正確例子:在 Macro 定義中, 務必為它的參數個別加上括號
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
printf("%d\n", SQUARE(10-5));
return 0;
}
不過遇到以下有side effect的例子就算加了括號也沒用。
錯誤例子: (感謝 yaca 網友提供)
#define MACRO(x) (((x) * (x)) - ((x) * (x)))
int main()
{
int x = 3;
printf("%d\n", MACRO(++x)); // 有side effect
return 0;
}
補充資料:
- https://stackoverflow.com/questions/14041453/why-are-preprocessor-
macros-evil-and-what-are-the-alternatives
- https://stackoverflow.com/questions/12447557/can-we-have-recursive-macros
- C11 Standard 6.10.3.4
- https://en.cppreference.com/w/cpp/language/lambda
10. 不要在 stack 設置過大的變數以避免堆疊溢位(stack overflow)
由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十KB,當變數所需的空
間過大時,很容易造成 stack overflow,程式亦隨之當掉(segmentation fault)。
可能造成堆疊溢位的原因包括遞迴太多次(多為程式設計缺陷),
或是在 stack 設置過大的變數。
錯誤例子:
int array[10000000]; // 在stack宣告過大陣列
std::array<int, 10000000> myarray; //在stack宣告過大std::array
正確例子:
C:
int *array = (int*) malloc( 10000000*sizeof(int) );
C++:
std::vector<int> v;
v.resize(10000000);
說明:建議將使用空間較大的變數用malloc/new配置在 heap 上,由於此時 stack
上只需配置一個 int* 的空間指到在heap的該變數,可避免 stack overflow。
使用 heap 時,雖然整個 process 可用的空間是有限的,但採用動態抓取
的方式,new 無法配置時會丟出 std::bad_alloc 例外,malloc 無法配置
時會回傳 null(註2),不會影響到正常使用下的程式功能
備註:
註1. 使用 heap 時,整個 process 可用的空間一樣是有限的,若是需要頻繁地
malloc / free 或 new / delete 較大的空間,需注意避免造成記憶體破碎
(memory fragmentation)。
註2. 由於Linux使用overcommit機制管理記憶體,malloc即使在記憶體不足時
仍然會回傳非NULL的address,同樣情形在Windows/Mac OS則會回傳NULL
(感謝 LiloHuang 補充)
補充資料:
- https://zh.wikipedia.org/wiki/%E5%A0%86%E7%96%8A%E6%BA%A2%E4%BD%8D
- https://stackoverflow.com/questions/3770457/what-is-memory-fragmentation
- https://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/
overcommit跟malloc:
- https://goo.gl/V9krbB
- https://goo.gl/5tCLQc
11. 使用浮點數千萬要注意精確度所造成的誤差問題
根據 IEEE 754 的規範,又電腦中是用有限的二進位儲存數字,因此常有可
能因為精確度而造成誤差,例如加減乘除,等號大小判斷,分配律等數學上
常用到的操作,很有可能因此而出錯(不成立)
更詳細的說明可以參考精華區 z-8-11
或參考冼鏡光老師所發表的一文 "使用浮點數最最基本的觀念"
https://blog.dcview.com/article.php?a=VmhQNVY%2BCzo%3D
12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞
(感謝 loveme00835 legnaleurc 版友的幫忙)
首先必須有個觀念,C 語言中陣列是無法直接拿來傳遞的!
不過這時候會有人跳出來反駁:
void pass1DArray( int array[] );
int a[10];
pass1DArray( a ); /* 可以合法編譯,而且執行結果正確!! */
事實上,編譯器會這麼看待
void pass1DArray( int *array );
int a[10];
pass1DArray( &a[0] );
我們可以順便看出來,array 變數本身可以 decay 成記憶體起頭的位置
因此我們可以 int *p = a; 這種方式,拿指標去接陣列。
也因為上述的例子,許多人以為那二維陣列是不是也可以改成 int **
錯誤例子:
void pass2DArray( int **array );
int a[5][10];
pass2DArray( a );
/* 這時候編譯器就會報錯啦 */
/* expected ‘int **’ but argument is of type ‘int (*)[10]’*/
在一維陣列中,指標的移動操作,會剛好覆蓋到陣列的範圍
例如,宣告了一個 a[10],那我可以把 a 當成指標來操作 *a 至 *(a+9)
因此我們可以得到一個概念,在操作的時候,可以 decay 成指標來使用
也就是我可以把一個陣列當成一個指標來使用 (again, 陣列!=指標)
但是多維陣列中,無法如此使用,事實上這也很直觀,試圖拿一個
pointer to pointer to int 來操作一個 int 二維陣列,這是不合理的!
儘管我們無法將二維陣列直接 decay 成兩個指標,但是我們可以換個角度想,
二維陣列可以看成 "外層大的一維陣列,每一維內層各又包含著一維陣列"
如果想通了這一點,我們可以仿造之前的規則,
把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列
void pass2DArray( int (*array) [10] ); // array 是個指標,指向 int [10]
int a[5][10];
pass2DArray( a );
這時候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢?
答案是它代表著 a[0] 外層的那一維陣列,裡面包含著內層 [0]~[9]
也因此 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9]
結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要
明確的指出陣列大小,這樣多維陣列的傳遞就不會有問題了
也因為剛剛的例子,我們可以清楚的知道在傳遞陣列時,實際行為是在傳遞
指標,也因此如果我們想用 sizeof 來求得陣列元素個數,那是不可行的
錯誤例子:
void print1DArraySize( int* arr ) {
printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
} /* 一個指標的大小 */
受此限制,我們必須手動傳入大小
void print1DArraySize( int* arr, size_t arrSize );
C++ 提供 reference 的機制,使得我們不需再這麼麻煩,
可以直接傳遞陣列的 reference 給函數,大小也可以直接求出
正確例子:
void print1DArraySize( int (&array)[10] ) { // 傳遞 reference
cout << sizeof(array) / sizeof(int); // 正確取得陣列元素個數
}
13. 函式內 new 出來的空間記得要讓主程式的指標接住
對指標不熟悉的使用者會以為以下的程式碼是符合預期的
void newArray(int* local, int size) {
local = (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
newArray(ptr, 10);
}
接著就會找了很久的 bug,最後仍然搞不懂為什麼 ptr 沒有指向剛剛拿到的合法空間
讓我們再回顧一次,並且用圖表示 (感謝Hazukashiine板友提供圖解)
┌────┐ ┌────┐ ┌────┐ ┌────┐
Heap │ │ │ │ │ 新配置 │ │ 已泄漏 │
│ │ │ │ │ 的空間 <─┐ │ 的空間 │
│ │ │ │ │(allocd)│ │ │(leaked)│
│ │ │ │ ├────┤ │ ├────┤
│ │ │ │ │ : │ │ │ │
│ │ │ │ │ : │ │ │ : │
│ │ ├────┤ ├────┤ │ │ : │
│ │ │ local ├─┐ │ local ├─┘ │ │
├────┤ ├────┤ │ ├────┤ ├────┤
Stack │ ptr ├─┐ │ ptr ├─┤ │ ptr ├─┐ │ ptr ├─┐
└────┘ ╧ └────┘ ╧ └────┘ ╧ └────┘ ╧
未初始化 函式呼叫 配置空間 函式返回
int *ptr; local = ptr; local = malloc();
用圖看應該一切就都明白了,我也不需冗言解釋
也許有人會想問,指標不是傳址嗎?
精確來講,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe)
local 接到了 ptr 指向的那個位置,接著函式內 local 要到了新的位置
但是 ptr 指向的位置還是沒變的,因此離開函式後就好像事什麼都沒發生
( 嚴格說起來還發生了 memory leak )
以下是一種解決辦法
int* createNewArray(int size) {
return (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
ptr = createNewArray(10);
}
改成這樣亦可 ( 為何用 int** 就可以?想想他會傳什麼過去給local )
void createNewArray(int** local, int size) {
*local = (int*) malloc( size * sizeof(int) );
}
int main() {
int *ptr;
createNewArray(&ptr, 10);
}
如果是 C++,別忘了可以善用 Reference
void newArray(int*& local, int size) {
local = new int[size];
}
後記:從「古時候」流傳下來一篇文章
"The Ten Commandments for C Programmers"(Annotated Edition)
by Henry Spencer
https://www.lysator.liu.se/c/ten-commandments.html
一方面它不是針對 C 的初學者,一方面它特意模仿中古英文
聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望
能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。
作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP
看板眾多網友提供寶貴意見及程式實例。
nowar100 多次加以修改整理,擴充至 13 項,並且製作成動畫版。
wtchen 應板友要求移除動畫並根據C/C++標準修改內容(Ver.2016)
如發現 Bug 請推文回報,謝謝您
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 90.41.184.140
※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1465304337.A.9F2.html
※ 編輯: wtchen (90.41.184.140), 06/07/2016 21:03:40
※ 編輯: wtchen (90.41.184.140), 06/07/2016 21:04:31
※ 編輯: wtchen (90.41.66.248), 06/10/2016 00:52:56
※ 編輯: wtchen (90.41.66.248), 06/11/2016 02:55:21
※ 編輯: wtchen (90.41.211.206), 11/06/2016 01:26:25
※ 編輯: wtchen (90.41.211.206), 11/07/2016 02:55:06
※ 編輯: wtchen (90.27.175.198), 11/23/2016 20:08:24
※ 編輯: wtchen (90.27.175.198), 11/23/2016 20:11:23
※ 編輯: wtchen (90.41.175.158), 03/07/2017 23:45:11
※ 編輯: wtchen (90.41.175.158), 03/08/2017 23:42:16
※ 編輯: wtchen (90.41.175.158), 03/08/2017 23:43:43
※ 編輯: wtchen (86.209.162.81), 03/09/2017 23:11:25
※ 編輯: wtchen (86.209.162.81), 03/10/2017 03:46:29
... <看更多>