1.獲得隨機數的方法:
rand()函數用來生成隨機數,但嚴格意義上來說只是生成了偽隨機數。在使用默認種子不變的情況下,如果在程序內循環,那么下一次生成隨機數時會調用上一次rand()的結果作為種子。但是使用默認種子不變的情況下,每次執行程序得到的隨機數都是相同的。
此時可使用srand()來指定種子數,通常用當前時間作為種子:srand(time(0))。具體情況如下:
#include <cstdlib>
#include <ctime>
srand(time(0));
while(1)
{
result = rand() % 100;
}
注意:千萬不能把srand()寫入循環當中,否則得出的隨機數與當前時間有關,同一秒內的隨機數是相同的。
2.位域(位段)
位域是一種數據結構,所謂位域是把一個字節中的二進位劃分為幾個不同的區域,并說明每個區域的為數;每個域有一個域名,允許在程序中按域名進行操作。如此一來,就能把幾個不同對象用一個字節的二進制位域來表示。
struct 位域結構名
{ 類型說明符 位域名:位域長度; };
(1)一個位域必須存儲在同一個字節中,不能跨兩個字節。如一個字節所剩空間不夠存放另一位域時,應從下一單元起存放該位域;當然也可以有意使某位域從下一單元開始。
struct bs
{
unsigned a:4;
unsigned :0; /*空域*/
unsigned b:4; /*從下一單元開始存放*/
unsigned c:4;
};
(2)位域的長度不能大于一個字節的長度,即不能超過8位。
(3)位域可以無位域名,這時它只用來填充或者調整位置,例如:
struct k
{
int a:1;
int :2; /*該2位不能使用*/
int b:3;
int c:2;
};
位域的使用方法和結構相同,沒有什么區別。
注意:對于位域的對齊方式,和結構類似。比如int a : 4,首先會給int類型分配4個字節,然后將高4位分給a,如果a前后變量也是int,可以合用這4個字節(除非已經超出4個字節的范圍)。最終整個位段結構也要根據最大長度的類型來對齊。
3.6種位運算符
& 按位與
| 按位或
^ 按位異或
~ 取反 //不會改變原值
<< 左移 //不會改變原值
>> 右移 //不會改變原值
4.數值分為整型和實型,整型就是不帶小數位的數,例如char、int等,實型則是指帶小數位的數,也稱浮點數,例如double、float等。
5.左值和右值
凡是可以被賦值的表達式,就叫左值,左值總是直接的或間接的變量(返回引用類型的函數也可以作為左值)。
凡是可以賦值給左值的,就叫右值,右值可以是變量、常量、函數調用等。
所有的左值都是右值,反之不見得。
注意:并不是左值就是可以賦值的值。可以被賦值的左值稱為modifiable l-values,不可賦值的左值稱為nonmodifiable l-values,包括有:
const int i=3; //i是左值,但是不可能被賦值
int a[10]; //a不可能被賦值
struct Node a; //一個struct或者union中包含const,不可賦值
++a=20; //a是一個變量,可被程序尋址,可以改變它的值
a++=20; //a++是一個引用了臨時對象的表達式,所以是右值
a+5=20; //a+5也是引用了臨時對象的表達式,不能尋址該對象,是右值
6.sizeof求長度
32位系統中,對于char a[10]這樣的數組來說,sizeof(a)的值為10;對于char *p=“12345”這樣的指針來說,sizeof(p)的值為4,要求p指向的字符串長度,需要用strlen函數。
對于char *p=malloc(100)這樣的指針來說,sizeof(p)的值為4,要求分配內存的大小,需要用到_msize函數,size_t _msize(void *)在#include <malloc.h>中。
對于函數總void Function(char a[100]),相當于void Function(char *a),所以sizeof(a)的值為4。
7.局部變量能和全局變量重名,則局部變量屏蔽全局變量。需要使用全局變量時使用"::"符號。“::”表示全局有效域。
8.
main()
{
char *c1 = "abc";
char c2[] = "abc";
char *c3 = ( char* )malloc(3);
c3 = "abc";
printf("%d %d %s\n",&c1,c1,c1);
printf("%d %d %s\n",&c2,c2,c2);
printf("%d %d %s\n",&c3,c3,c3);
getchar();
}
運行結果
2293628 4199056 abc
2293624 2293624 abc
2293620 4199056 abc
分析:
char *c1 = "abc"中的字符串是常量字符串,存儲在常量區中;char c2[] = "abc"的字符串存儲在棧中;char *c3 = ( char* )malloc(3)存儲在堆中;c3 = "abc"使得c3指向新的常量字符串,而不是對動態申請的內存進行更新;同時,c1,c2,c3這三個變量都是局部變量,只不過存儲的是指向的內存地址,占4個字節的長度。
&c2和c2指向同一個地址,只不過&c2指向的是char[3]數組類型,c2指向的是char類型;c1和c3指向同一個地址,因為c3指向新的字符串,而且經過編譯器優化后,與c1指向同一個字符串。
&c1、&c2、&c3的值分別為2293620、2293624、2293628,這是因為指針c1和c2占了4個字節的長度,而c2指向的數組恰好也占了4個字節的長度。
9.結構體對齊問題
結構體中成員對齊的條件是:
(1)每個成員按照自己的方式對齊,對齊規則是每個成員按照類型的大小和對齊參數(默認是8字節)中較小的一個來對齊;
(2)整體結構的默認對齊方式是按照最長的成員和對齊參數中較小的那個來對齊。
(3)總的來說,對齊方式無非是1、2、4、8、16……等。
設置對齊的預編譯命令是:
#pragma pack(1)
其中pack()中的數字表示對齊參數(字節數),不寫表示默認為8字節。
struct{
short a1;
short a2;
short a3;
}A;
//sizeof(A) = 6
struct{
long a1;
short a2;
}B;
//sizeof(B) = 8
#pragma pack(8)
struct S1{
char a;
long b;
};
struct S2 {
char c;
struct S1 d;
long long e;
};
//sizeof(S2) = 24
#pragma pack(1)
struct{
short a1;
short a2;
short a3;
}A;
//sizeof(A) = 6
struct{
long a1;
short a2;
}B;
#pragma pack(pop)
//sizeof(B) = 6
10.
靜態定義的變量對象稱為有名對象;動態定義的變量對象稱為無名對象。
動態定義的對象初始化:需要使用初始化式顯示初始化,且動態定義的類型數組不能初始化。
動態建立類數組時,必須要有缺省的構造函數,因為數組不可添加初始化式。
11.memcpy / memmove / strstr / strcpy
函數原型:void *memmove(void *dest, const void *source, size_t count)
返回值說明:返回指向dest的void *指針
參數說明:dest,source分別為目標串和源串的首地址。count為要移動的字符的個數
函數說明:memmove用于從source拷貝count個字符到dest,如果目標區域和源區域有重疊的話,memmove能夠保證源串在被覆蓋之前將重疊區域的字節拷貝到目標區域中。
函數原型:void *memcpy(void *dest, const void *source, size_t count);
返回值說明:返回指向dest的void *指針
函數說明:memcpy功能和memmove相同,但是memcpy中dest和source中的區域不能重疊,否則會出現未知結果。
原型:char *strstr(char *haystack, char *needle);
用法:#include <string.h>
功能:從字符串haystack中尋找needle第一次出現的位置(不比較結束符NULL)。
說明:返回指向第一次出現needle位置的指針,如果沒找到則返回NULL。
函數原型:char * strcpy(char * strDest,const char * strSrc);
char * strcpy(char * strDest,const char * strSrc)
{
if ((strDest==NULL)||(strSrc==NULL))
throw "Invalid argument(s)";
char * strDestCopy=strDest;
while ((*strDest++=*strSrc++)!='\0')
;
return strDestCopy;
}
memcpy和memmove 的區別:
函數memcpy()從source指向的區域向dest指向的區域復制count個字符,如果兩數組重疊,不定義該函數的行為。而memmove()如果兩函數重疊,賦值仍正確進行。memcpy函數假設要復制的內存區域不存在重疊,如果你能確保你進行復制操作的的內存區域沒有任何重疊,可以直接用memcpy;如果你不能保證是否有重疊,為了確保復制的正確性,你必須用memmove。memcpy的效率會比memmove高一些,如果還不明白的話可以看一些兩者的實現:
void *memmove(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));
char *tmp, *s;
if (dest <= src)
{
tmp = (char *) dest;
s = (char *) src;
while (count--)
*tmp++ = *s++;
}
Else
{
tmp = (char *) dest + count;
s = (char *) src + count;
while (count--)
*--tmp = *--s;
}
return dest;
}
12.++i與i++
int arr[] = {6,7,8,9,10};
int *ptr = arr;
*(ptr++) +=123;
Print(“%d %d”, *ptr, *(++ptr));
一定要注意求值順序,*(ptr++) +=123中先做加法6+123,然后ptr++,此時指針指向7;對于Print(“%d %d”, *ptr, *(++ptr)),從后往前執行,先做++ptr,指向8,然后輸出8和8。
注:一般禁止在語句中這樣使用++運算符,因為會依賴不同的編譯器而有所不同。例如,TC中printf語句從右向左執行,VC6.0中同樣從右向左執行,但是在遇見后增運算符a++或者a--時是在整個printf語句后才執行。
int i=8;
printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);
這個函數的輸出結果在VC里是8 7 8 8,在TC里是8 7 7 8。
int i=3, j;
j = (i++)*(i++);
這個表達式輸出j是9,i最后是5,因為兩個i++是在整個運算式之后才進行的。
int i=3, j;
j = (++i)*(++i);
這個表達式輸出j是25,i最后是5,因為++i相當于i加1后將i放入式子中,所以j=i*i,且i為5。
int i=3, j;
printf(“%d %d”, ++i, ++i);
這個表達式輸出5, 4,為什么兩個值不一樣呢?因為printf是函數,將++i傳給printf后,相當于打印的是形參,而兩個++i傳入的形參不同。
注:特別要注意宏、運算與函數使用i++和++i的情況,容易讓人困惑……
13.
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
return 0;
}
在C語言中,凡不加返回值類型限定的函數,就會被編譯器作為返回整型值處理;C++語言有很嚴格的類型安全檢查,不允許上述情況(指函數不加類型聲明)發生。可是編譯器并不一定這么認定,譬如在Visual C++6.0中上述add函數的編譯無錯也無警告且運行正確。
#include "stdio.h"
int fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
在C語言中,可以給無參數的函數傳送任意類型的參數,但是在C++編譯器中編譯同樣的代碼則會出錯。所以,無論在C還是C++中,若函數不接受任何參數,一定要指明參數為void。
按照ANSI(American National Standards Institute)標準,不能對void指針進行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:錯誤
pvoid += 1; //ANSI:錯誤
ANSI標準之所以這樣認定,是因為它堅持:進行算法操作的指針必須是確定知道其指向數據類型大小的。
例如:
int *pint;
pint++; //ANSI:正確
pint++的結果是使其增大sizeof(int)。
但是大名鼎鼎的GNU(GNU's Not Unix的縮寫)則不這么認定,它指定void *的算法操作與char *一致。
因此下列語句在GNU編譯器中皆正確:
pvoid++; //GNU:正確
pvoid += 1; //GNU:正確
pvoid++的執行結果是其增大了1。
在實際的程序設計中,為迎合ANSI標準,并提高程序的可移植性,我們可以這樣編寫實現同樣功能的代碼:
void * pvoid;
char* tmp = (char*)pvoid;
tmp++; //ANSI:正確;GNU:正確
tmp += 1; //ANSI:正確;GNU:正確
GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支持。但是我們在真實設計時,還是應該盡可能地迎合ANSI標準。