c++ 學習

一.獲取時間   請問如何計算一個程序運行的時間? 用什麼函數來獲得當前時間?
1.用difftime,秒級

#include <time.h>
#include <stdio.h>
#include <dos.h>

int main(void)
{
   time_t first, second;

   first = time(NULL);  /* Gets system   time */

  // 你的程序    ...............


   second = time(NULL); /* Gets system time    again */

   printf("The difference is: %f seconds/n",difftime(second,first));
   getch();

   return 0;
}

2.用DWORD GetTickCount(VOID)
CTime WINAPI GetCurrentTime( ) throw( );
獲得更精確的時間 GetTickCount

3.獲取系統編譯程序的時間
char* time1 = __DATE__;
char* time2 = __TIME__;
其中__DATE__和__TIME__是倆個宏。

二,IO算子

cout<<"Hello world!"<<flush<<endl; ---強制輸出
   cout<<endl;
   cout<<ends;          -----輸出一個null字符
   cout<<setfill('*');   -----和setw配合使用,將空白區用指定字符輸出
   cout<<setw(14)<<"how"<<setw(14)<<"how"<<endl;--set width
   double d = 33.11444;
   cout<<setprecision(4)<<d<<endl;----設置小數點的位數
   cout<<dec<<10<<endl;----一旦設定,後面的全部跟隨。
 
   cout<<hex<<10<<endl;
   cout<<oct<<10<<endl;
   setbase(n);----更改原來設置的進制。如更改爲16進制,setbase(16);
   int a;
   cin>>hex>>a;   ----以hex輸入
   cout<<dec<<a<<endl;
 
 
三、內存對齊問題(關鍵字,內存對齊)
typedef struct
{
 int a[10];
 char ch[10];
 int f[10];
 bool b[10];
}MType;


int _tmain(int argc, _TCHAR* argv[])
{

 int i;
    MType mt; 
 
 printf("%d",sizeof(MType));
 getch();
 return 0;
}
這個結構中,本來按我們計算sizeof(MType)是100,然而結果是104。在vc和
linux下的dev c++都是。
原因是內存對齊問題。一般編譯器認爲內存以4的倍數對齊。仔細分析內存可以得出
結論,不夠4,要向後移動,湊成4的倍數。這個機構的一個對象就涉及了向後移動2
字節,補全2字節。故爲104。

可以在文件最前面加#pragma pack(1) 這是將編譯器該成爲1的倍數對齊,這樣求出的
長度就爲100了。

四。分位定義
typedef unsigned char _uint_8;
typedef struct
{
 char lo4:4, //先定義的是低位,在內存中,高位在左, 低位在右。
      hi4:4;
}myByte;
int main()
{

 myByte mch;
 void* p;
 //mch.hig4 = 6;
 //mch.low4 = 1;
 p = &mch;
 //*((unsigned char*)p) = *((unsigned char*)p)>>4;
 *(unsigned char*)(p) = 0xf1;
 printf("%d /n", mch.hig4); //-1 結果
 printf("%d /n", mch.low4); //1
 
 *(unsigned char*)(p) >>= 4;
 
 printf("%d /n", mch.hig4); //0
 printf("%d /n", mch.low4); //-1
 
 
 return 0;
}
表示將一個字節分成倆部分,前面2位,後面6位。
使用:

 MyByte b;
 b.hi4 =  10;
 b.lo4 = 2;

 cout<<b.hi4<<endl;
 cout<<b.lo4<<endl;
注意:不能誇字節定義。必須定義在結構裏方可使用。

五,關於new


new"分配"的內存當然都在堆中。
但new並不僅僅只有分配內存的作用,它還可以在已有內存(包括棧內存)上強制重新
構造對象的作用。

如:
#include <iostream>
using namespace std;

class A
{
public:
 int i;
 int j;
 A(int _i , int _j) { i = _i; j = _j; }
};

int main()
{
 A a(3, 4);
 cout << a.i << a.j << endl;
 new (&a) A(5,6);
 cout << a.i << a.j << endl;

 return 0;
}

六、關於n個隨機數的和的問題
求m個隨機數之和爲n的算法。

void myRan(int n,int m)
{
 int i;
 int sum=0;
 int *a = new int[m];
 for (i=0; i<m; i++)
 {
  a[i] = 0;
 }
 for(i=0; i<m-1; i++)
 {
  a[i]=rand()%(n + 1-sum);
  sum+=a[i];
  cout<<a[i]<<'/t';
 }
 a[m-1]=n - sum;
 cout<<a[m-1]<<endl;
 delete []a;

}
七,關於結構的定義和賦值
typedef struct
{
   int a;
   short b[2];                  /*定義第一個結構體*/                   
}Ex2;
typedef struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定義第二個結構體*/
 Ex1 * d;
}Ex1;
我們看第二個結構,Ex1 必須寫在括號的前面,否這編譯不會通過,大概是不識別
結構種的Ex1符號吧。而後一個Ex1可以寫,也可以不寫。
如果結構種沒有自身的指針,那麼,機構的名字可以放在結構定義的末尾,
也可以是結構體的開始。 另外,結構體種不能用結構自身定義成員,可以用結構
的指針定義成員例如:
typedef struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定義第二個結構體*/
 Ex1 d;  //錯誤,可以用結構的指針,但是不能用結構自身定義對象。
}Ex1;

結構體的賦值:
對於上面的倆個結構:
typedef struct
{
   int a;
   short b[2];                  /*定義第一個結構體*/                   
}Ex2;
typedef struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定義第二個結構體*/
 Ex1 * d;
}Ex1;
上面是c語言的定義方式,在C++中,可以省略掉typedef,比如:
struct Ex1
{
    int a;
    char b[3];
    Ex2 c;                     /*定義第二個結構體*/
 Ex1 * d;
};
這時,c++編譯器可以識別出Ex1,並且可以直接用Ex1定義對象,不用再加strcut。
比如:Ex1 e1;

所以關鍵看是什麼編譯器,我用g++ 編譯後者沒有錯誤。用gcc就出現了好多錯誤。
可見後面的是c++支持的。在c++編程思想上有說明。Ch2,P12
可以這樣賦值。
 Ex2 e2 = {10,{10,10}};
 Ex1 e1={10,"Hi",{5,{25,25}},0};
 
八,關於內存問題:
從總體上程序的內存空間可分爲代碼區和數據區。
從C++的角度來看數據區又可作如下劃分:
1.
自動存儲區(棧):自動(局部)變量、寄存器變量(聲明的寄存器變量可能在寄存器
中,也可能在一般內存中。在邏輯上寄存器屬於自動存儲區。)、臨時對象以及函數參
數。
2.
靜態存儲區:全局對象、函數中的靜態變量、類中的靜態數據成員、常量字符串以及
namespace 變量(比如 namespace abc = std;中的 abc。)
3. 自由存儲區(堆):也稱爲動態內存。

一個可執行文件、.o、.a文件中系統規定的段有這麼多
* .bss
該sectiopn保存着未初始化的數據,這些數據存在於程序內存映象中。通過定義,當程
序開始運行,系統初始化那些數據爲0。該section不佔文件空間,正如它的section類
型SHT_NOBITS指示的一樣。
* .comment
該section保存着版本控制信息。
* .data and .data1
這些sections保存着初始化了的數據,那些數據存在於程序內存映象中。
* .debug
該section保存着爲標號調試的信息。該內容是未指明的。
* .dynamic
該section保存着動態連接的信息。該section的屬性將包括SHF_ALLOC位。是否需要SHF
_WRITE是跟處理器有關。第二部分有更詳細的信息。
* .dynstr
該section保存着動態連接時需要的字符串,一般情況下,名字字符串關聯着符號表的
入口。第二部分有更詳細的信息。
* .dynsym
該section保存着動態符號表,如“Symbol Table”的描述。第二部分有更
詳細的信息。
* .fini
該section保存着可執行指令,它構成了進程的終止代碼。
因此,當一個程序正常退出時,系統安排執行這個section的中的代碼。
* .got
該section保存着全局的偏移量表。看第一部分的“Special
Sections”和第二部分的“Global Offset Table”獲得更多的信息。
* .hash
該section保存着一個標號的哈希表。看第二部分的“Hash Table”獲得更多的信息。
* .init
該section保存着可執行指令,它構成了進程的初始化代碼。
因此,當一個程序開始運行時,在main函數被調用之前(c語言稱爲main),系統安排執
行這個section的中的代碼。
* .interp
該section保存了程序的解釋程序(interpreter)的路徑。假如在這個section中有一個
可裝載的段,那麼該section的屬性的SHF_ALLOC位將被設置;否則,該位不會被設置。
看第二部分獲得更多的信息。
* .line
該section包含編輯字符的行數信息,它描述源程序與機器代碼之間的對於關係。該sect
ion內容不明確的。
* .note
該section保存一些信息,使用“Note Section”(在第二部分)中提到的格式。
* .plt
該section保存着過程連接表(Procedure Linkage Table)。看第一部分的``Special
Sections''和第二部分的“Procedure Linkage Table”。
* .rel<name> and .rela<name>
這些section保存着重定位的信息,看下面的``Relocation''描述。
假如文件包含了一個可裝載的段,並且這個段是重定位的,那麼該section的
屬性將設此,一個重定位的section適用的是.text,那麼該名字就爲.rel.text或者是.
rela.text。
* .rodata and .rodata1
這些section保存着只讀數據,在進程映象中構造不可寫的段。看第二部分的`Program
Header''獲得更多的資料。
* .shstrtab
該section保存着section名稱。
* .strtab
該section保存着字符串,一般地,描述名字的字符串和一個標號的入口相關
聯。假如文件有一個可裝載的段,並且該段包括了符號字符串表,那麼section的SHF_A
LLOC屬性將被設置;否則不設置。
* .symtab
該section保存着一個符號表,正如在這個section裏``Symbol
Table''的描述。假如文件有一個可裝載的段,並且該段包含了符號表,那麼section的
SHF_ALLOC屬性將被設置;否則不設置。
* .text
該section保存着程序的``text''或者說是可執行指令。

前綴是點(.)的section名是系統保留的,儘管應用程序可以用那些保留的section名。
應用程序可以使用不帶前綴的名字以避免和系統的
sections衝突。object文件格式可以讓一個定義的section部分不出現在上面的列表中
。一個object文件可以有多個同樣名字的 section。
引用:
簡單地說有如下結構
命令行參數和環境變量


未初始化數據段bss
初始數據段
正文段


C程序一直由下列部分組成:

1)正文段——CPU執行的機器指令部分;一個程序只有一個副本;只讀,防止程序由於意
外事故而修改自身指令;
2)初始化數據段(數據段)——在程序中所有賦了初值的全局變量,存放在這裏。
3)非初始化數據段(bss段)——在程序中沒有初始化的全局變量;內核將此段初始化爲
0。
4)棧——增長方向:自頂向下增長;自動變量以及每次函數調用時所需要保存的信息(
返回地址;環境信息)。
5)堆——動態存儲分。

|-----------|
| |
|-----------|
| 棧 |
|-----------|
| | |
| /|/ |
| |
| |
| /|/ |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|


http://community.csdn.net/Expert/topic/3215/3215569.xml?temp=.3182337

       low address
   +--------------------------------+
   |   _TEXT class 'CODP'           |
   |        code                    |
   +--------------------------------+--
   |   _DATA class 'DATA'           |
   |     initialized data           |
   +--------------------------------+ DGROUP
   |   _BSS class 'BSS'             |
   |     uninitialized data         |
   +--------------------------------+--
   |                                |
   |     FREE SPACE                 |
   +--------------------------------+
   |                                |
   |     STACK                      |
   +--------------------------------+
   |                                |
   |     HEAP                       |
   +--------------------------------+
   |                                |
   |     FREE SPACE                 |
   +--------------------------------+
         high address

九、怎麼快速檢測出一個巨大的鏈表中的死鏈?我實在沒有想出好辦法,
只能說,沒辦法,只能一個一個的比了...各位高手有沒有什麼好辦法?
死鏈:鏈表的某個接點的next指針指向了曾經出現過的接點,導致鏈表死循環。
剛纔忘了說:該鏈表是單鏈。

老邁的解決辦法,很牛:
以前的帖子裏有這道題的簡化版的答案,我再貼一次吧,呵呵。
如果只是判斷鏈表中是否有迴路而不用具體求出迴路的具體路徑的話,則有一個取巧的
辦法。
開兩個遍歷鏈表的指針,一個一次往前走一個結點,另一個往前走兩個結點,
當這兩個指針相遇時,如果該接點的next==null,則無迴路,否則說明有死鏈。

十,關於句柄類的信息,看c++編程思想Ch3,p12。

一,
爲分削得人憔悴,
跺上高樓,
跺上高樓,
原來衆人都在樓上。

二,關於數組的賦值:
struct Node
{
 int n;
 float f;
 char ch;
};
int main()
{
 int str[20] = {1,50,3};//剩餘元素都爲0。只要有個,剩餘都爲0;
 //但是如果沒有初始化,int str[20];這時,數組並不初始化爲0。而是任意值
 Node nodeArr[20] = {{1,1.1,'a'}};//在沒有構造函數的情況下,
 //和前目數組一樣,剩餘節點都初始化爲0。
 //除第一個節點外,後面的個元素{0,0.0000,0}
 int i;
 for(i=0; i<20; i++)
 {
   printf("%d  ",str[i]);
   printf("%d  %f  %c/n",nodeArr[i].n, nodeArr[i].f, nodeArr[i].ch);
 }
 printf("/n");
 return 0;
}

但是在有了構造函數的情況下,用法就不同上面:
struct Node
{
 int n;
 float f;
 char ch;
 Node();
 
};
Node::Node()//默認構造函數
{
 n=1;
 f = 1.1;
 ch = 'b';
}
int main()
{
 //int str[20] = {1,50,3};
 //Node nodeArr[20] =
{{1,1.1,'a'}};//這種用法編譯錯誤,要求用構造函數構造對象
 Node nodeArr[20] = {};// 每個Node對象調用默認構造函數,內部成員分別爲
         // 1,1.1, 'b'
 //可見:如果沒有構造函數,就用與c相同的語法編譯,
 //如果有了構造函數,就必須用構造函數進行數組的初始化。這裏就構和類是一樣

 int i;
 for(i=0; i<20; i++)
 {
   //printf("%d  ",str[i]);
   printf("%d  %f  %c/n",nodeArr[i].n, nodeArr[i].f, nodeArr[i].ch);
 }
 printf("/n");
 return 0;
}

四,一個有趣的面試題:
哪爲高手能幫幫我,
在不使用if,while,do~~while等語句,不使用關係運算符,不使用MAX(),MIN()
的情況下,怎樣比較輸入的a,b兩個數的大小。(注:實現並不知道輸入的數的大小)
float max(float a,float b)
{
   float t1,t2;
   t1 = a + b;
   t2 = sqrt((a-b)*(a-b));//免得你說fabs中也用了判斷語句
   return (t1 + t2)/2;
}
這裏藏着很好的道理。t1 是二者的和,t2是大着比小者大的部分。如果把大出的
部分加到小者的上面,正好是2倍的大者。

三,關於省缺參數的定義。
如果一個程序只有一個cpp文件,而且這個函數沒有函數聲名,只有函數定義,那麼
這個函數的省缺參數可以放到函數定義中。比如:這種情況:
#include <iostream>
using namespace std;
void fun1(int m = 3, char ch = 'b')
{}
int main ()
{

   fun1();
   fun2();
   return 0;
}

如果一個程序,有頭文件,在頭文件中有函數聲名,或者沒有頭文件,但是有函數聲名
這種情況下,省缺參數必須放在函數的聲名中,不能放在函數定義中,否則編譯錯誤。
比如:
//links.h
void fun2(int m=3,char ch='a');


//links.cpp
#include <iostream>
#include "links.h"
using namespace std;
void fun1(int m = 3, char ch = 'b');//有聲名,必須在聲名處給出省缺參數
void fun2(int m/*=3*/,char
ch/*='a'*/)//聲名中給出,再不能在定義中設置省缺參數
{
}
void fun1(int m /*=3*/, char ch/* =
'b'*)//聲名中給出,再不能在定義中設置省缺參數
{}
void fun3(int m=3, char ch='c')//沒有函數聲名,可以在定義時給出省缺參數
{}
int main ()
{

   fun1();
   fun2();
   fun3();
   return 0;
}

四,分佈式系統
分佈式系統是通過通信網絡將物理上分佈的具有自治功能的數據處理系統或計算機
系統互聯起來,實現信息交換和資源共享,協作完成任務。分佈式系統要求一個統
一的操作系統,實現系統操作的統一性。分佈式操作系統管理分佈式系統中的所有
資源,它負責全系統的資源分配和調度,任務劃分,信息傳輸控制協調工作,併爲
用戶提供一個統一的界面,用戶通過這一界面實現所需要的操作並使用系統資源,
至於操作定在哪一臺計算機上執行或使用哪臺計算機的資源則是操作系統完成的,
用戶不必知道。此外,由於分佈式系統更強調分佈式計算和處理,因此對於多機合
作和系統重構,健壯性和容錯能力有更高的要求,要求分佈式操作系統有更短的相
應時間,更高吞吐量和更高可靠性。

五,冒泡排序算法:
void order(char* chs, const int& n)
{//冒泡排序算法,zhangggdlt
 int i,j;
 int maxSubscript;
 for (i=0; i<n; i++)//n-1
 {
  chs[0] = chs[1];
  maxSubscript = 1;
  for (j=2; j<=n-i; j++)
  {
   if (chs[0]<chs[j])
   {
    chs[0] = chs[j];
    maxSubscript = j;
   }
  }
  chs[maxSubscript] = chs[n-i];
  chs[n-i] = chs[0];
 }
 chs[0] = ' ';
}
六、關於命名空間
namespace A{
#define MIN 20
int k = 3;
}
其實,#define MIN 20 可以穿透命名空間,
也就是說不受命名空間限制,所以放入放出無所謂,建議放在外面。
構造函數不能是靜態函數。

七:關於多字節數據類型在內存中的存儲問題:
int ,short 分別是4、2字節。他們的存儲方式是:
int data = 0xf4f3f2f1;
其中低位存放在編址小的內存單元,高位存放在編址搞的內存單元
地址:0x8000      0x8001    0x8002   0x8003
數據: f1           f2         f3      f4

八、枚舉型定義,枚舉元素的值可以重複。
typedef enum
{
 monday=1, cat,tuesday=1,dog
}week;
用法:
week w = dog;
int k = tuesday;
上面都是可以的。

此時,dog的值是它前面的值加一,裏面有倆個元素值是相等的。同理,cat也一樣。
cat 和 dog的值都是2。

九,獲取時間,包括星期的程序
#include<time.h>
#include <stdio.h>


int main(){
char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得當地時間*/
printf ("%d.%d.%d ", (1900+p->tm_year),(1 +p->tm_mon), p->tm_mday);
//注意月份載localtime是從0。。11 代表1到12月,所以要加1。
printf("%s  %d:%d:%d/n", wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec);
return 0;
}

十、各個分析
#include <iostream>
using namespace std;

class A{
 int x;
};

class B:public A
{public:
    int b1;
 B(){b1=2;}
 int geti(){
  cout<<"geti called,b1 is "<<b1<<endl;
  return 1;
 }
};

int main()
{
 
 A a;
 B b;
 int k;
 B* pb=(B*)&a;
 pb = (B*)&k;
 pb->geti();
 int i;
    cin>>i;
 return 0;


}

分析:
你的類A是基類,類B是子類。
把基類的對象a的地址,強制轉換成子類的地址。這樣很危險,會導致數據裁減。其實
是更本不希望這樣做的。強制類型轉換,可以將任何類型都可以轉換,雖然語法沒有錯
誤,但是語意上沒有意義。比如:我們可以
int k;
B* pb=(B*)&k;
這樣編譯也沒有錯誤,但是,更本沒有意義。

你的轉換同樣也是一個道理。你的a對象裏跟本就沒有B類函數geti所要用的東西,但是
對於計算機來說,它不管這些,它只要對指針所指的內存進行操作就行了,它認爲從指
針所指的位置開始,後面就是一個B類對象。所以編譯可以通過。
但是沒有意義。

一、數字加密算法DES(Data Encryption Standard)

二、在linux下,最後連接成可執行文件時,如果連接的是一般的.o文件,則整個文件
的內容都會被裝入可執行文件中;如果連接的是庫,則只是從庫中找到程序中用到的變
量和函數,將他們裝入可執行文件中,那些放在庫中但是沒有被程序所引用的變量和函
數則不會被連接到可執行文件中。所以使用庫可以節省大量的開發時間,寫較大的程序
時,把程序模塊放到庫中。

三、一個類型問題
int PacketInfo::packetLen(const void* packet)
{
 u_int_8 tempByte[4]; //use to store、exchange the info from the
packet
 int ethType;
 int packetLen = 0;
 memcpy(tempByte, (u_int_8*)packet+12, 2);
 ......
}
我得函數中要實現內存拷貝,memcpy的原型是定義函數  void * memcpy (void * dest
,const void *src, size_t n);
 我開始用memcpy(tempByte, packet+12,
2);編譯報告錯誤,後來換成memcpy((void*)tempByte, (void*)packet+12, 2);
 也不能, 原來錯誤在一個空類型指針和一個數字相加,
不能得到類型所佔空間大小,於是無法確定packet+12的地址。
還有這個例子:
void memecopy(void* dp, const void* sp, int num)
{
 int i=0;
 while(i<num)
 {
  *((unsigned char*)dp)++ = *((unsigned char*)sp)++;
  i++;
 }
}
開始我沒有注意,只是*dp++ = *sp++; 呵呵, 結果當然編譯不過去,對一個void指針加加,
系統當然不知道幹什麼了。

四、在c++中, 函數沒有層次關係,可以有下面的函數調用情況。
void funB();

void funA()
{
 funB();
}
void funB()
{
 funA();
}
只要函數調用前對被調用函數聲名了就可以了。

五、內聯函數的使用限制
內聯函數目的是爲了提高函數執行效率(當用到內聯函數時,
不進行函數調用,而是類似於宏替換),內聯函數定義有一下限制。
1、函數中不可以有靜態變量
2、函數中不能太複雜,不能有switch、goto等語句
3、不能遞歸
4、不能定義數組。
如果出現了上述情況,編譯器會自動忽略了inline,將其編譯爲一般函數。

六、在內存中, 數是以補碼形式存在的, 整數的補碼仍然爲整數,
負數的補碼是正數的各位(包括符號位)取反加1。
比如:
#include <stdio.h>
int main()
{
 char ch = -3; //-1:11111111   -2:11111110 -127:10000001 -128:10000000
 unsigned char value;
 for(int i=0; i<8; i++)
 {
  value = ((ch<<i) & 0x80)>>7; //11111101
  printf("%d ", value);
 }
 printf("/n");
 return 0;
}

七、linux 下 ip地址轉換函數:
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>

  int inet_aton(const char *cp, struct in_addr *inp);

 in_addr_t inet_addr(const char *cp);

 in_addr_t inet_network(const char *cp);

 char *inet_ntoa(struct in_addr in);

 struct in_addr inet_makeaddr(int net, int host);

 in_addr_t inet_lnaof(struct in_addr in);

 in_addr_t inet_netof(struct in_addr in);

七、關於逗號算符
int t;
int i=0;
t=i++,++i,i++;
//結果t=1; i=3;
在c++課本上說, 逗號操作是強迫從左向右賦值。整個語句的值是最後一個分操作。
比如:
int a = 3;
a=3*5, a*4;//語句的結果是60
(a=3*5, a*4),a+5;//結果是20
int a=2, b=3, c=4;
print("%d, %d, %d/n", (a,b,c), b, c);
打印的第一個整數是(a,b,c),它的值是最後一個,所以是c,
最後的答案是:
4, 3, 4

也就是說, 上面的語句分成四部分,第一部分爲t=i++, 第二部分爲++i,第三部分爲i++.
還有下面的例子:
int a[] = {1,2,3,4,5};
int *i=a,k;
int b = 0;
b = k=*i++,printf("%d->",k),k=*++i,printf("%d->",k),k=*i++,printf("%d->/n",k);
printf("%d/n", b);
輸出爲:
1->3->3->
1
(完)。

七.一(相關上面,printf()的怪異)
int array[5]={8,5,13,55,6};
int *p=array;
printf("%d,%d,%d/n",*p++,*++p,*p++);
printf("%d,%d/n",*p,++*p);
for(int i=0;i<5;i++)
 printf("%d,",array[i]);
結果爲:
13,13,8
56,56
8,5,13,56,6,
非常不解,以後總結。
找到答案了,和一個同學討論的結論。
在c++的函數中, 函數在壓棧時, 參數是從右到左壓棧的, 比如:
void pp(int a, int b, int c)
{
 int d;
 int e;
 d = 0;
 e = 2;
}
int main()
{
 ...
 pp(...);
 
}
這時, 函數pp的棧中有pp的參數, main返回地址, 和現場信息。
pp的參數壓棧是:c, b , a。這樣, 先壓c, 那麼要先算c, 也就是說先算最後
一個參數,對於函數printf("%d,%d,%d/n",*p++,*++p,*p++);來說,先算後一個*pp++,
得出0(c 最後一個參數),再算中間一個*++p得出2(b 中間一個參數), 最後算第一
個*p++(a 第一個參數)得出2。所以a=2, b= 2, c=0。
打出的是220。

好像printf()裏如果涉及到每個變元參與運算,那麼輸出就要倒過來。

七.一 再談printf()
printf 的格式中, 如果出現了/0 /數字(八進制的1到3位) /x數字的情況處理(十六進制
的1到2位)。
/0當然是字符串的結束標誌了。
/101  這時, /與後面的1到三位八進制數字表示一個字符。注意,是按八進制算的, 不是
十進制。
/x61  /與後面的十六進制表示一個字符。
printf("/101 /x41");
結果是:
A A


八、關於sizeof問題

void Func(char a[100])
{
 char b[100];
 
 printf("%d/n", sizeof(a)); // 4字節而不是100字節
 printf("%d/n", sizeof(b)); //100
 
}
分析:倆個一樣,不用的原因要追蹤到函數的傳參問題。當數組作爲參數傳入函數時,
這個數組作爲指針傳入,也就是所說的傳地址。所以前面的是4,而不是100。

九、符號的結合方向。
在c++中, 運算符一般是從左向右結合的,只有賦值符是特例,從右向左結合。
比如:
a+b+c*d  從左向右結合
a = b = c =d;  從左向右結合。

十、再輪++、--操作。
int i = 3, k;
k = (++i)+(++i)+(++i);
k的值是18。原因是每個操作數每個操作數先++, 最後算加法。
i++三次成了6,最後是三個六相加,得18
int i=3, k;
k=(i++)+(i++)+(i++);
結果是9。原因是i先算加法,三個三相加,最後++,i=6。


一、在c語言中規定,凡不加類型說明得函數,一律自動按整型處理。在函數中,如果
返回類型與函數類型不同,則以函數類型爲準,對數值型數據,可以自動進行數據類型
轉換,即函數類型決定返回類型。如下:
max(x,y)
{
 float x, y, z;
 z = x>y?x:y;
 return (Z);
 
}
main()
{
 float a, b;
 int c;
 scanf("%f, %f",&a, &b);
 c = max(a,b);
 printf("Max is %d/n", c);
 
}
結果是:
1.5,2,5
Max is 2
max(x,y) 沒有明顯定義類型,按整型處理,返回一定是整型。
在c函數中,如果沒有返類型和return,這是並不是說函數真沒有return,而是要返回
一個用戶無用得值,這個值不確定。比如:
//test.c
#include <stdio.h>
#include <string.h>
pp()
{}
pp1()
{
}
main()
{
 int c = 0;
 c = pp();
 printf("%d/n", c);

 c = pp1();
 printf("%d/n", c);
}
輸出爲:
0
2
這倆個值不確定。
如果函數不想要返回,則要用void 定義函數類型。這樣就沒有返回值了。

二、c函數定義的一般形式:
無參函數定義:
類型標識符 函數名()
{說明部分
   語句
}

有參函數定義的一般形式:
類型標識符 函數名字(形式參數列表)
形式參數說明
{說明部分
 語句
}
如:
int max(x,y)
int x,y; //形式參數說明部分
{int z;  //函數體中的說明部分
 z=x>y?x:y;
 return (z);
}

函數調用說明:
如果函數參數列表中包含多個實參,各系統對於實參求值的順序是不確定的,有的系統
自左向右順序求實參,有的自右向左,許多c版本(Turbo C 和MS C)是按自右向左的順
序求值。這就導致了前面的printf("%d %d %d /n", *p++, *++p, *p++);的結論。

三、宏定義的範圍
#define G 9.18
   ^
   |
   |  G的有效區域。
   |
   v

#undef G
宏定義後面不能加分號, 否則連分號一起作爲宏定義的部分。宏定義不做類型檢查,
在預編譯時處理,只有展開後纔會根據替換來決定是否正確。

四、函數參數和函數的局部變量
void pp(char str1[])
{
 int i;
 for(i=0; i<10; i++)
  printf("%c ", *str++); //1
 char str2[20];
 for(i=0; i<10; i++)
  printf("%c ", *str2++); //2
}
在這個函數中,函數的參數str1是一個數組,函數的局部變量str2也是一個數組。
但是在1處,編譯是正確的,但是在2處,編譯通不過。它們都是數組,但是,函數
參數如果是數組,那麼當作地址處理,所以str1可以作爲一個指針變量處理。可以用
地址的++運算,但是對於局部變量str2,str2是一個數組,數組的地址是一個常量,
不能++。
這和前面提到的sizeof的例子是一個道理。
void pp(int str[10])
{
 printf("%d /n", sizeof(str)); //4  只是地址。
 int str2[10];
 printf("%d /n", sizeof(str2)); //40   數組的大小。
}

五、指向函數的指針。
一般形式爲:  數據類型 (*指針名字)(參數鏈表);
在c語言中,不要求參數鏈表,只在調用時候需要。但是c++中必須要求在函數指針定義
的時候給出參數鏈表。
比如:
//test.c
#include <stdio.h>
#include <string.h>
void pp(int a)
{
 printf("ok/n");
}
int main()

 void (*p)(); //c的函數指針定義, 可以不寫出參數鏈表,沒有錯誤。c++不行。
 p = pp; //在給函數指針賦值時,只需要給出函數名字而不必要給出參數
 (*p)(); //通過函數指針調用函數
 return 0;
}

//test.cpp
#include <stdio.h>
#include <string.h>
void pp(int a)
{
 printf("OK!/n");
}
int main()

 int a = 3;
 void (*p)();
 p = pp; //錯誤,必須定義:void (*p)(int);纔可以。
 //p = p1;
 (*p)(a);
 return 0;
}
c的定義並不嚴格, 但是c++嚴格的多了,同樣,c的函數可以不定義返回類型,但是
c++必須定義。

六、函數指針做函數參數
把函數名字作爲參數傳遞給一個函數,這樣的好處是:比較靈活,比如一個函數sub需
要調用倆個函數,如果直接在sub函數中調用,那麼sub函數就寫死了,如果想在sub中
調用一系列函數中的任意倆個,這時用函數指針做參數,可以根據賦值的不同而調用不同
的函數。
例如:
sub(int x, int y, int(*fun)());
{
 ....
 (*fun)();
}
....
int fun1()
{
 ...
}
main()
{
 int a,b;
 sub(a, b, fun1);
}

七、關於指針和數組聯合的定義:

int (*pointer1)[4];
int *pointer2[4];
看二者的區別: 前面pointer1是指向一個有四個元素的一維數組的指針;後面pointer2,
一個是定義一個有四個元素的一維指針數組,數組的每個元素是一個指針變量。

八、回調函數淺談

這是一種類似根據不同條件調用同一個返回值+參數類型一致的不同函數實現的機制.

針對Windows的消息機制一般回調函數指窗口函數, 根據不同消息都使用該窗口函數進
行操作.針對用戶自己的應用也可以建立起相應的消息機制, 類似VB裏面的事件
1. 聲明一個函數指針
2. 提供函數實現的一方在初始化的時候將函數指針初始化到相應的實現函數註冊到調
用者
3. 在特定事件/條件發生的時候, 調用者使用函數指針進行調用.
這就是回調函數的一般操作模式, 其實你如果學習COM就會發現, 裏面
的ConnectionPoint也是利用這種方式進行的, 客戶端註冊自己的事件處理子程序給
服務隊組件, 組件通過調用這些函數觸發事件, 其實回調的精髓就是你規定函數形
式(返回值, 參數)我提供實現和具體的調用地址.

九、對回調函數的例子:
//test.cpp
#include <stdio.h>
typedef void (*pcb)(int); //爲了簡化函數指針類型的變量定義,提高程序的可
       //讀性,我們需要把函數指針類型自定義一下。
       
void pp(int a) //回調函數,回調函數可以象普通函數一樣被程序調用,
{    //但是隻有它被當作參數傳遞給被調函數時才能稱作回調函數。
 printf("OK!%d/n", a);
}

void called(int n, pcb p) //被調函數,因爲它被用戶程序調用,所以叫被調函數
{        //我想是這樣,用戶在調用上面的函數時,
        //需要自己實現一個pcb類型的回調函數:
 printf("OKK");
 (*p)(n);
}
int main()

 int n = 1;
 called(n, pp);  //把pp當作一個變量傳遞給called函數
      
 return 0;
}

一個類的成員如果想做回調函數,那麼該成員一定要爲靜態成員,比如:
#include <stdio.h>
typedef void (*pcb)(int n);
class A
{
public:
  static void getN(int n)  //該成員一定爲static, 否則編譯錯誤
  {
   printf("OK Get it!/n");
  }
};

void pp(int n, pcb p)
{
 (*p)(n);
}
int main()

 A a;
 int n = 3;
 pp(n, a.getN);
 return 0;
}

參考:http://www-900.ibm.com/developerWorks/cn/linux/l-callback/index.shtml

                                                 (待續)


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章