1. c語言程序設計:通訊錄程序設計
#include "stdlib.h"
#include "string.h"
#include "conio.h"
#include "stdio.h"
#include "dos.h"
FILE *fp;
int i; //i是全局變數 可代替length
struct address
{ char postnum[10];
char a[40]; //家庭地址
};
struct birthday
{ int year;
int month;
int day;
};
struct ffriend
{ int num; //序號
char name[20];
char sex;
char telephone[13];
struct birthday birthday;
struct address address;
}
friends[50];
void Load()
{
int j;
long k;
fp=fopen("friend.txt","a+"); //打開文件friend.txt
if(fp!=NULL)
{
for(i=1;i<50;i++)
{
j=fgetc(fp);
if(j==EOF) //判斷是否到了文件尾
return;
k=i-1;
fseek(fp,k*sizeof(struct ffriend),SEEK_SET);
fread(&friends[i],sizeof(struct ffriend),1,fp); //從文件中讀取一條記錄
}
}
else
{
fp=fopen("friend.txt","w");
i=1;
}
}
void Show(int j)
{
// friends[j].num=i;
printf("\n\n\t編號-Nnumber: %3d",friends[j].num);
printf("\n\t姓名-Name:%-20s",friends[j].name);
printf("\n\t性別-Sex:%c",friends[j].sex);
printf("\n\t聯系電話-Telephone:%-13s",friends[j].telephone);
printf("\n\t出生日期-Birthday:%4d-%2d-%3d",friends[j].birthday.year,friends[j].birthday.month,friends[j].birthday.day);
printf("\n\t郵編-Postnum:%-10s",friends[j].address.postnum);
printf("\n\t通信地址-address:%-40s",friends[j].address.a);
}
void Append(int j)
{
fflush(stdin); //區內清除文件緩沖區,文件以寫方式打開時將緩沖容寫入文件
//stdin是一個標准FILE*(文件型指針)指向通常是用鍵盤的輸入的輸入流
friends[j].num=j;
printf("\n\t\t\t序號-Number:%d",j);
printf("\n\t\t\t姓名-Name:");
scanf("%s",friends[j].name);
fflush(stdin); //為什麼沒有fflush(stdin); 的效果會自動跳過呢?
printf("\t\t\t性別-Sex(m/w):"); //為什麼輸入漢字男女也會自動跳過聯系電話呢
scanf("%c",&friends[j].sex);
printf("\t\t\t聯系電話-telephone:");
scanf("%s",friends[j].telephone);
printf("\t出生日期-birthday");
printf("\n\t\t\t年份-year:");
scanf("%d",&friends[j].birthday.year);
printf("\t\t\t月份-month:");
scanf("%d",&friends[j].birthday.month);
printf("\t\t\t日-day:");
scanf("%d",&friends[j].birthday.day);
printf("\t\t\t郵編-Postnumber:");
scanf("%s",friends[j].address.postnum);
printf("\t\t\t通信地址-Address:");
scanf("%s",friends[j].address.a);
getchar();
}
void Delete()
{
int k;
printf("\n\tDelete 序號-Number:");
scanf("%d",&k);
if(k<=i)
{
for(int j=k;j<i+1;j++) /*插入位置後的元素順序後移*/
{
strcpy(friends[j].name,friends[j+1].name); /*交換元素內容*/
friends[j].sex=friends[j+1].sex;
strcpy(friends[j].telephone,friends[j+1].telephone);
friends[j].birthday.year=friends[j+1].birthday.year;
friends[j].birthday.month=friends[j+1].birthday.month;
friends[j].birthday.day=friends[j+1].birthday.day;
strcpy(friends[j].address.postnum,friends[j+1].address.postnum);
strcpy(friends[j].address.a,friends[j+1].address.a);
}
i--;
}
else
{
printf("輸入的序號太大!");
}
}
void Modify(int j)
{
Append(j);
}
void Save()
{
int j;
fp=fopen("friend.txt","w");
for(j=1;j<=i;j++)
{
fwrite(&friends[j],sizeof(struct ffriend),1,fp);
}
fclose(fp);
}
void main()
{
int j;
char grade;
char searchname[10];
Load();
i--;
do
{
printf("\t\t\t\t簡易通訊錄\n\n");
printf("功能選擇(Function choose)\n");
printf("\1A.讀取(Read)\n");
printf("\2B.增添(Append)\n");
printf("\6C.插入(Insert)\n");
printf("\5D.刪除(Delete)\n");
printf("\5E.查詢(Search)\n");
printf("\6F.修改(Modify)\n");
printf("\2G.保存(Save)\n");
printf("\1H.退出(Quit)\n");
printf("請選擇(Choice)\n注:輸入A~H的大寫字母\n");
scanf("%c",&grade);
switch(grade)
//加個大寫和小寫 都可以啊
{
case 'A': j=1; //顯示功能
while(getchar()!=0x1b&&j<=i) //增添按Esc鍵退出
{
Show(j++);
printf("\n請按回車鍵繼續!");
}
if(j-1<1)
{
printf("\n\t空文檔,無任何記錄-Empty Note\n");
printf("\n請按回車鍵繼續!");
getchar();
}
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'B': //增加功能
while(i<=50) //增加按ESC鍵退出的功能
{
i++;
Append(i);
printf("\t是否繼續增加?y/n");
//修改 加上else if 其他就跳出或強制列印出出錯
char a=getchar();
if(a=='n'||a=='N')
break;
}
if(i==51)
printf("\n\t文檔已滿,無法增加記錄-note full");
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
getchar();
break;
case 'C':
int k;
printf("\n\t輸入要插入的位置:");
scanf("%d",&k);
j=i+1;
friends[j].num=j;
for(j;j>k;j--) //插入功能
{
strcpy(friends[j].name,friends[j-1].name); //數據後移
friends[j].sex=friends[j-1].sex;
strcpy(friends[j].telephone,friends[j-1].telephone);
friends[j].birthday.year=friends[j-1].birthday.year;
friends[j].birthday.month=friends[j-1].birthday.month;
friends[j].birthday.day=friends[j-1].birthday.day;
strcpy(friends[j].address.postnum,friends[j-1].address.postnum);
strcpy(friends[j].address.a,friends[j-1].address.a);
}
Append(k);
i++;
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'D': //刪除功能 //增添隨意刪除多條記錄的功能
Delete();
if(i<1)
printf("\n沒有記錄-No records\n");
printf("\n請按回車鍵繼續!");
getchar();
getchar();
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'E': //查詢功能
printf("\n\t請輸入要查詢記錄的相關姓名:"); //增添查詢按列號的
scanf("%s",searchname);
for(j=1;j<=i;j++)
{
if(strcmp(searchname,friends[j].name)==0) //比較字元串
{
Show(j);
break;
}
}
if(i<1)
{
printf("\n 沒有您所查詢的記錄-No records");
printf("\n 請按回車鍵繼續!");
getchar();
}
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
getchar();
break;
case 'F': //修改功能 / /增添按序列號查詢
printf("\n\t請輸入要修改記錄的相關姓名:");
gets(searchname);
scanf("%s",searchname);
for(j=1;j<=i;j++)
{
if(strcmp(searchname,friends[j].name)==0) //比較字元串
{
Modify(j);
// getchar(); //加個輸出確定修改嗎
}
}
if(i<1) //修改功能
{ printf("\n 沒有您所要修改的記錄-No records");
printf("\n 請按回車鍵繼續!");
getchar();
getchar();
}
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'G':
Save(); //存檔功能
getchar();
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
break;
case 'H': //退出功能
char x;
printf("是否保存?yes/no\n");
scanf("%s",&x);
//getchar();
// getchar();
if(x=='y')
{
Save();
exit(0);
}
else
exit(0);
default:
printf("\n\t輸入有誤,請輸入正確的序列號!");
printf("\n請按回車鍵繼續!");
getchar();
printf("\n\t\t\t\t\t\t\t此次操作結束");
printf("\n\t------------------------------------------------------------\t\t\n");
}
}while(1);
}
2. 將C語言里的源程序畫流程圖, 流程圖里的語言有嚴格要求么 比如乘號 除號怎麼寫, 還有C語言源程
流程圖是程序員之間交流的工具,並不能交給計算機自動轉換成程序,沒有特別嚴格的規范。
你可以用Visio畫流程圖。
就寫 a % 2 == 0 就可以了,也可以寫 a mod 2 == 0,反正是給人看的。
矩形框里可以寫多個過程,甚至包括概括性的描述,但是注意是從上而下順序執行的。
3. c源程序不能表示的數制是
c源程序不能表示的數制是:二進制。
C語言源程序不能表示二進制,在C語言中,所有的數值型常量都帶有符號,所以整型數量只區別整型和長整型兩種形式,整型常量和長整型常量均可用十進制、八進制、十六進制3種形式表示。
在程序運行過程中,其值不能被改變的量稱為常量。常量也分為整型、實型、字元型等。以上是常量所具有的類型屬性,這些類型決定了各種常量所佔存儲空間的大小和數的表示範圍。在C程序中,常量是直接以自身的存在形式體現其值和類型。
(3)c語言程序裝單體號能不能舉報擴展閱讀
二進制對計算機的重要性:
二進制是計算技術中廣泛採用的一種數制。二進制數據是用0和1兩個數碼來表示的數。它的基數為2,進位規則是「逢二進一」,借位規則是「借一當二」,由18世紀德國數理哲學大師萊布尼茲發現。
當前的計算機系統使用的基本上是二進制系統,數據在計算機中主要是以補碼的形式存儲的。【計算機中的二進制則是一個非常微小的開關,用「開」來表示1,「關」來表示0】
20世紀被稱作第三次科技革命的重要標志之一的計算機的發明與應用,因為數字計算機只能識別和處理由『0』.『1』符號串組成的代碼。其運算模式正是二進制。
19世紀愛爾蘭邏輯學家喬治布爾對邏輯命題的思考過程轉化為對符號"0''.''1''的某種代數演算,二進制是逢2進位的進位制。0、1是基本算符。因為它只使用0、1兩個數字元號,非常簡單方便,易於用電子方式實現。
4. c語言編程 不能和百度一樣
部分 1: 准備工作
1
下載和安裝編譯器。 C語言需要通過編譯器解釋為計算機可以理解的機器碼。 編譯器通常是免費的, 不同的操作系統上一般使用不同的編譯器。
對於Windows系統, 可以嘗試 Microsoft Visual Studio Express 或者MinGW。
對於Mac系統, XCode是一款優秀的C語言編譯器。
對於Linux, gcc是一個不錯的選擇。
2
理解基本概念。 C語言是一門古老的語言, 但卻十分強大。它最初是為Unix操作系統設計的, 但後來被移植到了幾乎所有的操作系統上,並得到了很多擴展。C語言的現代版本是C++。
C語言本質上是由函數構成的,在函數體中你可以使用變數,條件語句,循環等語句來存儲和處理數據。
3
查看一些基本的代碼。 下面是用C語言寫的一段非常基本的代碼, 閱讀這些代碼,嘗試理解這種語言的不同部分是如何工作的, 並對程序的運作原理有初步的認識。 "
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
getchar();
return 0;}
"[1]
這里的#include 指令在程序開始之前就出現了, 它的功能是把包含有你需要的函數的庫載入進來。 在這個例子中,引入stdio。h 使得我們能夠使用 printf() 和 getchar() 這兩個函數。
這里的 main() 指令會告訴編譯器,程序需要運行一個叫做「main」的函數,該函數運行完畢後返回一個整數值。所有的C語言都要運行一個「main」函數。
{} 符號表示括弧內的所有內容都是函數的一部分。在本例中,他們標記了所有的內容都是「main」函數的一部分。
printf() 函數能夠將小括弧中的內容顯示在用戶的屏幕上。雙引號保證了這個字元完全按照字面的樣子輸出, \n 組合告訴編譯器這串字元輸出完之後將游標移動到下一行 。
; 符號表示一行的結束。絕大部分C代碼都以分號結束。
getchar()函數告訴編譯器這段程序要等待一個按鍵的輸入才能夠繼續。考慮到大部分編譯器在運行完程序之後會立刻關掉程序窗口,這個功能還是很有用的,因為這樣可以讓程序保持運行直到有鍵被按下才會結束。
return 0 指令表示程序的結束。請注意」main」函數是一個int類型的函數,也就是說當函數結束時需要返回一個整數。如果返回0則表示程序正確的執行了,其他數字表示程序運行時發生了錯誤。
4
嘗試編譯這段程序。 把上面的代碼輸入到你的代碼編輯器中,然後保存為」*。c」文件。 用你的編譯器編譯它, 一般來說點擊Build或Run按鈕即可。
5
要養成寫注釋的習慣。 注釋是代碼中的一部分,它不會被編譯, 但是卻可以告訴你代碼做了些什麼。這對於提醒你自己你的代碼是干什麼的以及讓其他開發者理解你的代碼都很有幫助。
在C語言中添加註釋只需要把要注釋的部分的前面添加/*, 後面添加 */。
不要吝嗇你的注釋,除了特別簡單明了的地方都盡量加上注釋吧。
注釋功能也可以快速的屏蔽一部分代碼但不刪除它們。只需要給你想要排除的代碼用注釋標簽包起來它們就不會被編譯。如果你想要改回來,去掉注釋標簽即可。
END
部分 2: 變數的使用
1
理解變數的功能。 變數是用來存儲數據的,不管是計算得出的還是用戶輸入的數據。變數在使用前要先定義,並且有不同的類型可以選擇。
有以下幾種常見的變數類型 int, char, 和 float。 每種變數類型都代表一種數據存儲的格式。
學習聲明變數。 變數在使用前要先被創建出來,或者叫」聲明」。 聲明一個變數只需要在變數類型的後面寫出變數的名字即可。比如,下面就是一些變數聲明的例子: "
float x;
char name;
int a, b, c, d;
"
注意,你可以在一行中聲明多個變數,只要它們的類型是一樣的就行,你只需用逗號把變數名隔開即可。
和大多數的C代碼一樣,變數的聲明也要以分號結尾。
理解在何處聲明變數。 變數的聲明必須要放在每個代碼塊之前(代碼塊是指用大括弧{}包起來的一段代碼)。如果你在代碼塊後邊聲明變數,程序就不能正確執行了。
用變數來存儲用戶的輸入。 現在你了解到了變數的一些基本原理, 你可以寫一段簡單的程序來存儲用戶的輸入。這次你需要用到另外一個叫scanf的函數, 它的功能是把用戶的輸入賦值給指定的變數。 "
#include <stdio。h>
int main()
{
int x;
printf( "請輸入一個數: " );
scanf( "%d", &x );
printf( "你輸入了: %d", x );
getchar();
return 0;
}
"
這里的"%d"符號告訴scanf函數在用戶的輸入中找出整數。
x前面的&符號告訴scanf在哪裡找到要修改的變數,並把輸入的整數值存進去。
最後的printf命令讀出輸入的整數並返回給用戶。
處理變數。 你可以用數學表達式來處理之前存儲的變數。需要注意一個重要的差別:在數學表達式中單個=是賦值號,作用是把等號右邊的值賦給等號左邊的變數, 而==則是比較兩個變數是否相等。 "
x = 3 * 4; /* 把x設為3*4,也就是12 */
x = x + 3; /* 把x的值增加3,然後把新的值賦值給x */
x == 15; /* 檢查x是否等於15 */
x < 10; /* 檢查x是否小於10 */
"
END
部分 3: 使用條件語句
1
理解條件語句的基本概念。大多數程序都是由條件語句驅動的, 這樣的語句可以判斷一個條件是TRUE(真)還是FALSE(假), 然後據此執行不同的動作。最基本的條件語句是if語句。
C語言中的TRUE和FALSE和你平常理解的有點不太一樣。TRUE和任何非0的數總是相等的。當你執行一個比較時,如果結果是TRUE,會返回一個」1」。如果結果是FALSE,會返回0。弄清楚這一點能幫助你更好的理解IF語句的執行過程。
2
學習基本的比較符號。條件語句是以比較大小的數學表達式為核心的。下面列出了最常用的一些比較符號: "
> /* 大於 */
< /* 小於 */
>= /* 大於或等於 */
<= /* 小於或等於 */
== /* 等於 */
!= /* 不等於 */
"
"
10 > 5 TRUE
6 < 15 TRUE
8 >= 8 TRUE
4 <= 8 TRUE
3 == 3 TRUE
4 != 5 TRUE
"
3
寫一個IF語句。 利用IF語句,你可以根據一個表達式計算的結果決定之後的程序如何運行。之後學習了其他條件語句後你可以把它們組合起來實現更強大的功能,不過現在寫一段簡單的代碼熟悉一下就行了。 "
#include <stdio.h>
int main()
{
if ( 3 < 5 )
printf( "3比5小");
getchar();
}
"
4
使用ELSE/ELSE IF語句來擴展你的條件判斷。 在IF語句中你可以添加ELSE 和ELSE IF語句來處理更多不同的結果。 ELSE後面的語句在IF中的判斷結果為FALSE時執行。 ELSE IF則可以讓你在一個代碼塊中使用多個IF語句來處理更多的情況。閱讀下面的代碼看一下他們是怎麼工作的。 "
#include <stdio.h>
int main()
{
int age;
printf( "請輸入您的年齡: " );
scanf( "%d", $age );
if ( age <= 12 ) {
printf( "你是個孩子!\n" );
}
else if ( age < 20 ) {
printf( "年輕的感覺真好!\n" );
}
else if ( age < 40 ) {
printf( "你充滿了青春的活力!\n" );
}
else {
printf( "充滿智慧的年紀! \n" );
}
return 0;
}
"[2]
這段代碼接收用戶輸入的一個數據然後傳遞給IF語句。如果這個數據滿足第一個條件,則第一個printf被執行。如果沒有滿足第一個條件,則後面的各個ELSE IF會逐個進行判斷直到有一個滿足條件的分支為止。如果沒有任何分支滿足條件,則ELSE語句被執行。
END
部分 4: 學習循環語句
1
理解循環的原理。 循環是編程中很重要的一部分, 它們讓你可以重復執行一段代碼直到滿足特定條件為止。這個機制使你可以很容易的實現重復的動作,同時省去了每次做條件判斷的麻煩。
有3種類型的循環:FOR, WHILE, 和 DO…WHILE。
2
使用FOR循環。這是最常見和好用的循環類型。它會不斷的運行循環內的函數直到循環條件不再成立。FOR循環需要包含3條語句:初始化變數,循環條件,和變數更新的方式。如果你不需要其中的某個語句,把該處空著打一個分號即可,否則的話循環會無限運行。[3] "
#include <stdio.h>
int main()
{
int y;
for ( y = 0; y < 15; y++;){
printf( "%d\n", y );
}
getchar();
}
"
在上面的程序中,y被設為0,循環繼續運行的條件是y小於15。每次循環中y的值被列印出來,並且被增加1。一旦y=15,循環就結束了。
3
使用WHILE循環。WHILE循環比FOR循環要簡單的多。它們只有一個語句,只要該語句為TRUE循環就不斷執行。你不需要初始化或更新變數,不過你可以在循環體中做這些事。 "
#include <stdio.h>
int main()
{
int y;
while ( y <= 15 ){
printf( "%d\n", y );
y++;
}
getchar();
}
"
這個循環每執行一次,y++命令就把y的值增加1。一旦y達到16,循環就結束了。(記住只有在y小於等於15的條件下循環才會執行。)
4
使用DO…WHILE 循環。這種循環在你想要確保一個循環至少要被執行一次時非常管用。在FOR和WHILE循環中,循環條件的檢測是在循環開始之前進行的,這也就意味著有可能第一次檢測就無法通過,那樣的話循環體一次都不會被執行。然而DO。。。WHILE循環會先執行一次循環體然後再做檢測,這就保證了循環體至少會被執行一次。 "
#include <stdio.h>
int main()
{
int y;
y = 5;
do {
printf("循環被執行!\n");
} while ( y != 5 );
getchar();
}
"
在上面的循環中,即使循環條件檢測的結果為FALSE還是會展示一條信息。變數y的值被設為5而WHILE循環被設置為只有當y 不等於5時才運行,所以循環執行到條件檢測時就會終止。但信息還是被展示出來了,因為條件檢測是在輸出信息之後的。
DO…WHILE循環中的WHILE語句必須以;結尾。這是唯一一種循環體以分號結尾的情形。
END
部分 5: 使用函數
1
理解函數的基本原理。 函數是可以被程序的其他部分調用的自成一體的代碼塊。使用函數可使你更容易重復一段代碼,同時也讓程序變得簡單易讀、便於修改。函數中可以包含前面提到的所有技術,甚至可以包含其他函數。
前面的例子中的main()就是一個函數,同樣getchar()也是。
要想寫出高效且易讀的代碼,函數是至關重要的。用好函數可以使你的程序條理更清晰。
2
從函數原型開始。在真正開始編寫一個函數之前,你最好先搞清楚你要完成什麼功能,並從函數原型開始編寫。函數的基本語法格式為: 「返回值類型 函數名 (參數1, 參數2, …);」。 比如下面是一個把兩個數相加的函數: "
int add ( int x, int y );
"
上面的代碼創建了一個把輸入的x和y相加然後返回他們的和的函數。
3
把函數添加到程序中。你可以用上面的函數原型實現一個把用戶輸入的兩個數相加的函數。下面的程序展示了"add"函數是如何處理輸入的數字的。 "
#include <stdio。h>
int add ( int x, int y );
int main()
{
int x;
int y;
printf( "請輸入要求和的兩個數: " );
scanf( "%d", &x );
scanf( "%d", &y );
printf( "您輸入的數字之和為 %d\n" add( x, y ) );
getchar();
}
int add ( int x , int y )
{
return x + y;
}
"
請注意,函數的原型也需要放在程序的頂部,這樣能保證當這個函數被調用時編譯器已經知道存在這個函數,同時也知道它的返回類型。不過只有你想在函數調用處之後再實現這個函數時才有必要這么做。如果你直接把add()函數的實現放在main()函數之前,那麼即使不聲明函數原型也是一樣的。
這個函數的實現代碼其實是放在程序的底部的。main() 函數獲取了用戶輸入的兩個整數並把他們傳給add()函數以便後者進行處理,然後add()函數把計算的結果返回給main() 。
當add()函數被定義之後,你就可以在程序中的任何地方調用它了。
END
部分 6: 不斷學習
找一些C語言編程相關的書來看。 這篇指南涵蓋了C語言中最基礎的部分,但對於完整的C語言只是體系來說這只是皮毛。如果能有一本好的參考書你在學習C語言的道路上能省去許多麻煩
加入一些社區。不論是在線上還是線下,都有一些很棒的致力於學習和發展優秀編程語言的社區。如果能找到一些志同道合的C語言程序員,並和他們相互交流, 你一定能進步的很快。
如果可能的話還可以嘗試黑客馬拉松活動。在這項活動參賽的團體或個人需要在有限的時間里對給出的問題提出自己的程序和解決方案,因此很能培養人的創造力。你還可以籍此認識許多優秀的程序員。並且世界各地都有規律性舉辦的黑客馬拉松活動。
參加一些課程。雖然你沒必要重新回到學校修得計算機科學的學位,但是適當的參加一些相關課程還是會讓你的學習過程有質的飛躍。沒有什麼能比一位C語言專家的言傳身教更能幫助你了。通常你總能在網路上找到一些培訓課程,也有一些專業的計算機培訓機構可供選擇。還有一些大學的優秀課程是免費對外開放的,你可以去旁聽。
考慮學習C++。 如果你已經掌握了C語言,了解一下C++將對你大有裨益。因為C++是C語言更現代的版本, 它更加的靈活和方便。C++是以面向對象的思想設計的,掌握C++之後你就可以在幾乎所有操作系統中編寫強大的程序了。
5. C語言與JAVA編寫程序的語言可以互用嗎
不可以!但是兩者的演算法思想是一樣的!但兩者的語法略有區別,所以不能直接用!
6. 請問,可以用C語言編寫測試程序,來測試java用Swing開發的軟體嗎
要看你怎麼測試了
單體測試還是系統測試
如果做單體測試,還是用Junit吧
如果是系統測試,就人工測試
7. C語言,哪位好心的大哥,姐姐:能告述我位運算嗎我看不懂啊!
位運算簡介及實用技巧(一):基礎篇
什麼是位運算?
程序中的所有數在計算機內存中都是以二進制的形式儲存的。位運算說穿了,就是直接對整數在內存中的二進制位進行操作。比如,and運算本來是一個邏輯運算符,但整數與整數之間也可以進行and運算。舉個例子,6的二進制是110,11的二進制是1011,那麼6 and 11的結果就是2,它是二進制對應位進行邏輯運算的結果(0表示False,1表示True,空位都當0處理):
110
AND 1011
---------------
0010 --> 2
由於位運算直接對內存數據進行操作,不需要轉成十進制,因此處理速度非常快。當然有人會說,這個快了有什麼用,計算6 and 11沒有什麼實際意義啊。這一系列的文章就將告訴你,位運算到底可以干什麼,有些什麼經典應用,以及如何用位運算優化你的程序。
Pascal和C中的位運算符號
下面的a和b都是整數類型,則:
C語言 | Pascal語言
-------+-------------
a & b | a and b
a | b | a or b
a ^ b | a xor b
~a | not a
a << b| a shl b
a >> b | a shr b
注意C中的邏輯運算和位運算符號是不同的。520|1314=1834,但520||1314=1,因為邏輯運算時520和1314都相當於True。同樣的,!a和~a也是有區別的。
各種位運算的使用
=== 1. and運算 ===
and運算通常用於二進製取位操作,例如一個數 and 1的結果就是取二進制的最末位。這可以用來判斷一個整數的奇偶,二進制的最末位為0表示該數為偶數,最末位為1表示該數為奇數.
=== 2. or運算 ===
or運算通常用於二進制特定位上的無條件賦值,例如一個數or 1的結果就是把二進制最末位強行變成1。如果需要把二進制最末位變成0,對這個數or 1之後再減一就可以了,其實際意義就是把這個數強行變成最接近的偶數。
=== 3. xor運算 ===
xor運算通常用於對二進制的特定一位進行取反操作,因為異或可以這樣定義:0和1異或0都不變,異或1則取反。
xor運算的逆運算是它本身,也就是說兩次異或同一個數最後結果不變,即(a xor b) xor b = a。xor運算可以用於簡單的加密,比如我想對我MM說1314520,但怕別人知道,於是雙方約定拿我的生日19880516作為密鑰。1314520 xor 19880516 = 20665500,我就把20665500告訴MM。MM再次計算20665500 xor 19880516的值,得到1314520,於是她就明白了我的企圖。
下面我們看另外一個東西。定義兩個符號#和@(我怎麼找不到那個圈裡有個叉的字元),這兩個符號互為逆運算,也就是說(x # y) @ y = x。現在依次執行下面三條命令,結果是什麼?
x <- x # y
y <- x @ y
x <- x @ y
執行了第一句後x變成了x # y。那麼第二句實質就是y <- x # y @ y,由於#和@互為逆運算,那麼此時的y變成了原來的x。第三句中x實際上被賦值為(x # y) @ x,如果#運算具有交換律,那麼賦值後x就變成最初的y了。這三句話的結果是,x和y的位置互換了。
加法和減法互為逆運算,並且加法滿足交換律。把#換成+,把@換成-,我們可以寫出一個不需要臨時變數的swap過程(Pascal)。
procere swap(var a,b:longint);
begin
a:=a + b;
b:=a - b;
a:=a - b;
end;
好了,剛才不是說xor的逆運算是它本身嗎?於是我們就有了一個看起來非常詭異的swap過程:
procere swap(var a,b:longint);
begin
a:=a xor b;
b:=a xor b;
a:=a xor b;
end;
=== 4. not運算 ===
not運算的定義是把內存中的0和1全部取反。使用not運算時要格外小心,你需要注意整數類型有沒有符號。如果not的對象是無符號整數(不能表示負數),那麼得到的值就是它與該類型上界的差,因為無符號類型的數是用00到$FFFF依次表示的。下面的兩個程序(僅語言不同)均返回65435。
var
a:word;
begin
a:=100;
a:=not a;
writeln(a);
end.
#include <stdio.h>
int main()
{
unsigned short a=100;
a = ~a;
printf( "%d\n", a );
return 0;
}
如果not的對象是有符號的整數,情況就不一樣了,稍後我們會在「整數類型的儲存」小節中提到。
=== 5. shl運算 ===
a shl b就表示把a轉為二進制後左移b位(在後面添b個0)。例如100的二進制為1100100,而110010000轉成十進制是400,那麼100 shl 2 = 400。可以看出,a shl b的值實際上就是a乘以2的b次方,因為在二進制數後添一個0就相當於該數乘以2。
通常認為a shl 1比a * 2更快,因為前者是更底層一些的操作。因此程序中乘以2的操作請盡量用左移一位來代替。
定義一些常量可能會用到shl運算。你可以方便地用1 shl 16 - 1來表示65535。很多演算法和數據結構要求數據規模必須是2的冪,此時可以用shl來定義Max_N等常量。
=== 6. shr運算 ===
和shl相似,a shr b表示二進制右移b位(去掉末b位),相當於a除以2的b次方(取整)。我們也經常用shr 1來代替div 2,比如二分查找、堆的插入操作等等。想辦法用shr代替除法運算可以使程序效率大大提高。最大公約數的二進制演算法用除以2操作來代替慢得出奇的mod運算,效率可以提高60%。
位運算的簡單應用
有時我們的程序需要一個規模不大的Hash表來記錄狀態。比如,做數獨時我們需要27個Hash表來統計每一行、每一列和每一個小九宮格里已經有哪些數了。此時,我們可以用27個小於2^9的整數進行記錄。例如,一個只填了2和5的小九宮格就用數字18表示(二進制為000010010),而某一行的狀態為511則表示這一行已經填滿。需要改變狀態時我們不需要把這個數轉成二進制修改後再轉回去,而是直接進行位操作。在搜索時,把狀態表示成整數可以更好地進行判重等操作。這道題是在搜索中使用位運算加速的經典例子。以後我們會看到更多的例子。
下面列舉了一些常見的二進制位的變換操作。
功能 | 示例 | 位運算
----------------------+---------------------------+--------------------
去掉最後一位 | (101101->10110) | x shr 1
在最後加一個0 | (101101->1011010) | x shl 1
在最後加一個1 | (101101->1011011) | x shl 1+1
把最後一位變成1 | (101100->101101) | x or 1
把最後一位變成0 | (101101->101100) | x or 1-1
最後一位取反 | (101101->101100) | x xor 1
把右數第k位變成1 | (101001->101101,k=3) | x or (1 shl (k-1))
把右數第k位變成0 | (101101->101001,k=3) | x and not (1 shl (k-1))
右數第k位取反 | (101001->101101,k=3) | x xor (1 shl (k-1))
取末三位 | (1101101->101) | x and 7
取末k位 | (1101101->1101,k=5) | x and (1 shl k-1)
取右數第k位 | (1101101->1,k=4) | x shr (k-1) and 1
把末k位變成1 | (101001->101111,k=4) | x or (1 shl k-1)
末k位取反 | (101001->100110,k=4) | x xor (1 shl k-1)
把右邊連續的1變成0 | (100101111->100100000) | x and (x+1)
把右起第一個0變成1 | (100101111->100111111) | x or (x+1)
把右邊連續的0變成1 | (11011000->11011111) | x or (x-1)
取右邊連續的1 | (100101111->1111) | (x xor (x+1)) shr 1
去掉右起第一個1的左邊 | (100101000->1000) | x and (x xor (x-1))
最後這一個在樹狀數組中會用到。
Pascal和C中的16進製表示
Pascal中需要在16進制數前加$符號表示,C中需要在前面加0x來表示。這個以後我們會經常用到。
整數類型的儲存
我們前面所說的位運算都沒有涉及負數,都假設這些運算是在unsigned/word類型(只能表示正數的整型)上進行操作。但計算機如何處理有正負符號的整數類型呢?下面兩個程序都是考察16位整數的儲存方式(只是語言不同)。
var
a,b:integer;
begin
a:=00;
b:=01;
write(a,' ',b,' ');
a:=$FFFE;
b:=$FFFF;
write(a,' ',b,' ');
a:=FFF;
b:=00;
writeln(a,' ',b);
end.
#include <stdio.h>
int main()
{
short int a, b;
a = 0x0000;
b = 0x0001;
printf( "%d %d ", a, b );
a = 0xFFFE;
b = 0xFFFF;
printf( "%d %d ", a, b );
a = 0x7FFF;
b = 0x8000;
printf( "%d %d\n", a, b );
return 0;
}
兩個程序的輸出均為0 1 -2 -1 32767 -32768。其中前兩個數是內存值最小的時候,中間兩個數則是內存值最大的時候,最後輸出的兩個數是正數與負數的分界處。由此你可以清楚地看到計算機是如何儲存一個整數的:計算機用00到FFF依次表示0到32767的數,剩下的00到$FFFF依次表示-32768到-1的數。32位有符號整數的儲存方式也是類似的。稍加註意你會發現,二進制的第一位是用來表示正負號的,0表示正,1表示負。這里有一個問題:0本來既不是正數,也不是負數,但它佔用了00的位置,因此有符號的整數類型範圍中正數個數比負數少一個。對一個有符號的數進行not運算後,最高位的變化將導致正負顛倒,並且數的絕對值會差1。也就是說,not a實際上等於-a-1。這種整數儲存方式叫做「補碼」。
位運算簡介及實用技巧(二):進階篇(1)
===== 真正強的東西來了! =====
二進制中的1有奇數個還是偶數個
我們可以用下面的代碼來計算一個32位整數的二進制中1的個數的奇偶性,當輸入數據的二進製表示里有偶數個數字1時程序輸出0,有奇數個則輸出1。例如,1314520的二進制101000000111011011000中有9個1,則x=1314520時程序輸出1。
var
i,x,c:longint;
begin
readln(x);
c:=0;
for i:=1 to 32 do
begin
c:=c + x and 1;
x:=x shr 1;
end;
writeln( c and 1 );
end.
但這樣的效率並不高,位運算的神奇之處還沒有體現出來。
同樣是判斷二進制中1的個數的奇偶性,下面這段代碼就強了。你能看出這個代碼的原理嗎?
var
x:longint;
begin
readln(x);
x:=x xor (x shr 1);
x:=x xor (x shr 2);
x:=x xor (x shr 4);
x:=x xor (x shr 8);
x:=x xor (x shr 16);
writeln(x and 1);
end.
為了說明上面這段代碼的原理,我們還是拿1314520出來說事。1314520的二進制為101000000111011011000,第一次異或操作的結果如下:
XOR
---------------------------------------
得到的結果是一個新的二進制數,其中右起第i位上的數表示原數中第i和i+1位上有奇數個1還是偶數個1。比如,最右邊那個0表示原數末兩位有偶數個1,右起第3位上的1就表示原數的這個位置和前一個位置中有奇數個1。對這個數進行第二次異或的結果如下:
XOR
---------------------------------------
結果里的每個1表示原數的該位置及其前面三個位置中共有奇數個1,每個0就表示原數對應的四個位置上共偶數個1。一直做到第五次異或結束後,得到的二進制數的最末位就表示整個32位數里有多少個1,這就是我們最終想要的答案。
計算二進制中的1的個數
同樣假設x是一個32位整數。經過下面五次賦值後,x的值就是原數的二進製表示中數字1的個數。比如,初始時x為1314520(網友抓狂:能不能換一個數啊),那麼最後x就變成了9,它表示1314520的二進制中有9個1。
x := (x and 555555) + ((x shr 1) and 555555);
x := (x and 333333) + ((x shr 2) and 333333);
x := (x and F0F0F0F) + ((x shr 4) and F0F0F0F);
x := (x and FF00FF) + ((x shr 8) and FF00FF);
x := (x and 00FFFF) + ((x shr 16) and 00FFFF);
為了便於解說,我們下面僅說明這個程序是如何對一個8位整數進行處理的。我們拿數字211(我們班某MM的生日)來開刀。211的二進制為11010011。
+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <---原數
+---+---+---+---+---+---+---+---+
| 1 0 | 0 1 | 0 0 | 1 0 | <---第一次運算後
+-------+-------+-------+-------+
| 0 0 1 1 | 0 0 1 0 | <---第二次運算後
+---------------+---------------+
| 0 0 0 0 0 1 0 1 | <---第三次運算後,得數為5
+-------------------------------+
整個程序是一個分治的思想。第一次我們把每相鄰的兩位加起來,得到每兩位里1的個數,比如前兩位10就表示原數的前兩位有2個1。第二次我們繼續兩兩相加,10+01=11,00+10=10,得到的結果是00110010,它表示原數前4位有3個1,末4位有2個1。最後一次我們把0011和0010加起來,得到的就是整個二進制中1的個數。程序中巧妙地使用取位和右移,比如第二行中333333的二進制為00110011001100....,用它和x做and運算就相當於以2為單位間隔取數。shr的作用就是讓加法運算的相同數位對齊。
二分查找32位整數的前導0個數
這里用的C語言,我直接Copy的Hacker's Delight上的代碼。這段代碼寫成C要好看些,寫成Pascal的話會出現很多begin和end,搞得代碼很難看。程序思想是二分查找,應該很簡單,我就不細說了。
int nlz(unsigned x)
{
int n;
if (x == 0) return(32);
n = 1;
if ((x >> 16) == 0) {n = n +16; x = x <<16;}
if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
n = n - (x >> 31);
return n;
}
只用位運算來取絕對值
這是一個非常有趣的問題。大家先自己想想吧,Ctrl+A顯示答案。
答案:假設x為32位整數,則x xor (not (x shr 31) + 1) + x shr 31的結果是x的絕對值
x shr 31是二進制的最高位,它用來表示x的符號。如果它為0(x為正),則not (x shr 31) + 1等於000000,異或任何數結果都不變;如果最高位為1(x為負),則not (x shr 31) + 1等於$FFFFFFFF,x異或它相當於所有數位取反,異或完後再加一。
高低位交換
這個題實際上是我出的,做為學校內部NOIp模擬賽的第一題。題目是這樣:
給出一個小於2^32的正整數。這個數可以用一個32位的二進制數表示(不足32位用0補足)。我們稱這個二進制數的前16位為「高位」,後16位為「低位」。將它的高低位交換,我們可以得到一個新的數。試問這個新的數是多少(用十進製表示)。
例如,數1314520用二進製表示為0000 0000 0001 0100 0000 1110 1101 1000(添加了11個前導0補足為32位),其中前16位為高位,即0000 0000 0001 0100;後16位為低位,即0000 1110 1101 1000。將它的高低位進行交換,我們得到了一個新的二進制數0000 1110 1101 1000 0000 0000 0001 0100。它即是十進制的249036820。
當時幾乎沒有人想到用一句位操作來代替冗長的程序。使用位運算的話兩句話就完了。
var
n:dword;
begin
readln( n );
writeln( (n shr 16) or (n shl 16) );
end.
而事實上,Pascal有一個系統函數swap直接就可以用。
二進制逆序
下面的程序讀入一個32位整數並輸出它的二進制倒序後所表示的數。
輸入: 1314520 (二進制為)
輸出: 460335104 (二進制為)
var
x:dword;
begin
readln(x);
x := (x and 555555) shl 1 or (x and $AAAAAAAA) shr 1;
x := (x and 333333) shl 2 or (x and $CCCCCCCC) shr 2;
x := (x and F0F0F0F) shl 4 or (x and $F0F0F0F0) shr 4;
x := (x and FF00FF) shl 8 or (x and $FF00FF00) shr 8;
x := (x and 00FFFF) shl 16 or (x and $FFFF0000) shr 16;
writeln(x);
end.
它的原理和剛才求二進制中1的個數那個例題是大致相同的。程序首先交換每相鄰兩位上的數,以後把互相交換過的數看成一個整體,繼續進行以2位為單位、以4位為單位的左右對換操作。我們再次用8位整數211來演示程序執行過程:
+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <---原數
+---+---+---+---+---+---+---+---+
| 1 1 | 1 0 | 0 0 | 1 1 | <---第一次運算後
+-------+-------+-------+-------+
| 1 0 1 1 | 1 1 0 0 | <---第二次運算後
+---------------+---------------+
| 1 1 0 0 1 0 1 1 | <---第三次運算後
+-------------------------------+
位運算簡介及實用技巧(三):進階篇(2)
今天我們來看兩個稍微復雜一點的例子。
n皇後問題位運算版
n皇後問題是啥我就不說了吧,學編程的肯定都見過。下面的十多行代碼是n皇後問題的一個高效位運算程序,看到過的人都誇它牛。初始時,upperlim:=(1 shl n)-1。主程序調用test(0,0,0)後sum的值就是n皇後總的解數。拿這個去交USACO,0.3s,暴爽。
procere test(row,ld,rd:longint);
var
pos,p:longint;
begin
if row<>upperlim then
begin
pos:=upperlim and not (row or ld or rd);
while pos<>0 do
begin
p:=pos and -pos;
pos:=pos-p;
test(row+p,(ld+p)shl 1,(rd+p)shr 1);
end;
end
else inc(sum);
end;
乍一看似乎完全摸不著頭腦,實際上整個程序是非常容易理解的。這里還是建議大家自己單步運行一探究竟,實在沒研究出來再看下面的解說。
和普通演算法一樣,這是一個遞歸過程,程序一行一行地尋找可以放皇後的地方。過程帶三個參數,row、ld和rd,分別表示在縱列和兩個對角線方向的限制條件下這一行的哪些地方不能放。我們以6x6的棋盤為例,看看程序是怎麼工作的。假設現在已經遞歸到第四層,前三層放的子已經標在左圖上了。紅色、藍色和綠色的線分別表示三個方向上有沖突的位置,位於該行上的沖突位置就用row、ld和rd中的1來表示。把它們三個並起來,得到該行所有的禁位,取反後就得到所有可以放的位置(用pos來表示)。前面說過-a相當於not a + 1,這里的代碼第6行就相當於pos and (not pos + 1),其結果是取出最右邊的那個1。這樣,p就表示該行的某個可以放子的位置,把它從pos中移除並遞歸調用test過程。注意遞歸調用時三個參數的變化,每個參數都加上了一個禁位,但兩個對角線方向的禁位對下一行的影響需要平移一位。最後,如果遞歸到某個時候發現row=111111了,說明六個皇後全放進去了,此時程序從第1行跳到第11行,找到的解的個數加一。
~~~~====~~~~===== 華麗的分割線 =====~~~~====~~~~
Gray碼
假如我有4個潛在的GF,我需要決定最終到底和誰在一起。一個簡單的辦法就是,依次和每個MM交往一段時間,最後選擇給我帶來的「滿意度」最大的MM。但看了dd牛的理論後,事情開始變得復雜了:我可以選擇和多個MM在一起。這樣,需要考核的狀態變成了2^4=16種(當然包括0000這一狀態,因為我有可能是玻璃)。現在的問題就是,我應該用什麼順序來遍歷這16種狀態呢?
傳統的做法是,用二進制數的順序來遍歷所有可能的組合。也就是說,我需要以0000->0001->0010->0011->0100->...->1111這樣的順序對每種狀態進行測試。這個順序很不科學,很多時候狀態的轉移都很耗時。比如從0111到1000時我需要暫時甩掉當前所有的3個MM,然後去把第4個MM。同時改變所有MM與我的關系是一件何等巨大的工程啊。因此,我希望知道,是否有一種方法可以使得,從沒有MM這一狀態出發,每次只改變我和一個MM的關系(追或者甩),15次操作後恰好遍歷完所有可能的組合(最終狀態不一定是1111)。大家自己先試一試看行不行。
解決這個問題的方法很巧妙。我們來說明,假如我們已經知道了n=2時的合法遍歷順序,我們如何得到n=3的遍歷順序。顯然,n=2的遍歷順序如下:
00
01
11
10
你可能已經想到了如何把上面的遍歷順序擴展到n=3的情況。n=3時一共有8種狀態,其中前面4個把n=2的遍歷順序照搬下來,然後把它們對稱翻折下去並在最前面加上1作為後面4個狀態:
000
001
011
010 ↑
--------
110 ↓
111
101
100
8. C語言中的;號怎麼使用
一條語句結束一般要用分號
如果你想幾條語句用一個分號也是可以的
例如幾條語句寫在一行上用逗號隔開
或者用邏輯符號隔開
下面是一個列印99表的例子
[Make@Make ~]$ cat hehe.c
#include <stdio.h>
int r;main(_){(++r>_?puts(""),r=0,_++^9:printf("%d*%d=%-4d",r,_,_*r))&&main(_);}
[Make@Make ~]$ gcc hehe.c
[Make@Make ~]$ ./a.out
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
[Make@Make ~]$
9. c語言中,普通字元常量單撇號下,是否可以用一個漢字,例如:『好』,這樣是否合法。
不合法,漢字是長位元組的,而''是短位元組。
漢字還有好幾種編碼方式,比如Unicode,「好」的字元就是由0xBA,0XC3兩個組合的。具體使用,可以用樓上的方式來用,但它的長度是短字元的兩倍。
上面的意思是告訴你,'好'是代表著一個整數,int類型的,不能轉換為char類型的,默認把單個漢字當著整數。
10. 關於c語言中變數有外部鏈接內部鏈接和無 鏈接怎麼理解
這部分內容是在程序的編譯和link層面談的。
一個大一些的工程往往不是只有一個程序文件,經常由好多C程序文件構成,有的時候裡面個別程序可能還用的其他語言,編碼完成後常常分別編譯,編譯完成再link到一起。某個C程序需要用到其他程序中定義過的變數,一般都加extern前綴,編譯時編譯器會預留訪問鏈接的空位,等到link階段再在整個工程的其他C編譯結果中去對號,把訪問鏈接填上。這就是外部鏈接。如果你程序全寫在一個文件里,那永遠都不會有外部鏈接。
內部鏈接常指一個程序文件中全局變數,可以被程序文件內各個子程序訪問,這在編譯過程中處理,和link階段不發生關系。如果變數前加了static,那麼它永遠不會被外部程序訪問,它不會被編譯程序寫入目標代碼的鏈接區。
無鏈接,就是在一個單體程序里,比如一個子程序,定義一個變數只給這個程序段用,那就是無鏈接。編譯器和link都不需要對這樣的變數做跨程序段的地址鏈接,這樣的變數都是直接分配寄存器或者近堆中的直接地址(每個子程序都有自己的基本存儲空間,被調用時得到分配,返回時被釋放,我習慣叫它近堆,標准叫啥早不記得了)。
變數是這樣,程序代碼段也大體差不多。每次在程序文件中調用一個文件內部的子程序,就產生一個內部鏈接;如果調用外部文件中的子程序,就產生一個外部鏈接。只有沒有任何子程序,所有代碼都寫在一個文件里的程序,才是無鏈接程序。
鏈接是個編譯和link層面的概念,所以不僅限於變數層面討論。
對了,再延伸,可以把一些子程序文件歸類,程序執行某部分任務才訪問,其他時間不訪問時,可以生成DLL。在程序執行那部分功能時,通過操作系統和DLL建立動態鏈接,當然這是外部鏈接,這也是程序設計中常用到的。這可以避免生成一個巨大的EXE,運行時吃掉過多的系統資源,還可以實現這個DLL中包含的子程序在操作系統級和其他程序共用。