• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            2012年6月3日

            C語言文件追加參數操作

            對文件進行讀寫是常碰到操作,文件在進行讀寫操作之前要先打開,使用完畢要關閉。所謂打開文件,實際上是建立文件的各種有關信息,并使文件指針指向該文件,以便進行其它操作。通過c語言基礎培訓可以基本掌握文件進行讀寫操作。
               
                文件的打開(fopen函數)
               
                fopen函數用來打開一個文件,其調用的一般形式為:文件指針名=fopen(文件名,使用文件方式); 其中,"文件指針名"必須是被說明為FILE 類型的指針變量;"文件名"是被打開文件的文件名;"使用文件方式"是指文件的類型和操作要求。 "文件名"是字符串常量或字符串數組。
               
                相關函數 :open,fclose
               
                表頭文件 :#include<stdio.h>
               
                定義函數 :FILE * fopen(const char * path,const char * mode);
               
                函數說明
               
                參數path字符串包含欲打開的文件路徑及文件名,參數mode字符串則代表著流形態。
               
                mode有下列幾種形態字符串:
               
                r 打開只讀文件,該文件必須存在。
               
                r+ 打開可讀寫的文件,該文件必須存在。
               
                w 打開只寫文件,若文件存在則文件長度清為0,即該文件內容會消失。若文件不存在則建立該文件。
               
                w+ 打開可讀寫文件,若文件存在則文件長度清為零,即該文件內容會消失。若文件不存在則建立該文件。
               
                a 以附加的方式打開只寫文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾,即文件原先的內容會被保留。
               
                a+ 以附加方式打開可讀寫的文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾后,即文件原先的內容會被保留。
               
                上述的形態字符串都可以再加一個b字符,如rb、w+b或ab+等組合,加入b 字符用來告訴函數庫打開的文件為二進制文件,而非純文字文件。不過在POSIX系統,包含Linux都會忽略該字符。由fopen()所建立的新文件會具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)權限,此文件權限也會參考umask 值。
               
                返回值
               
                文件順利打開后,指向該流的文件指針就會被返回。若果文件打開失敗則返回NULL,并把錯誤代碼存在errno 中。
               
                附加說明
               
                一般而言,開文件后會作一些文件讀取或寫入的動作,若開文件失敗,接下來的讀寫動作也無法順利進行,所以在fopen()后請作錯誤判斷及處理。
               
                范例
               
                #include<stdio.h>
               
                main()
               
                {
               
                FILE * fp;
               
                fp=fopen("noexist","a+");
               
                if(fp= =NULL) return;
               
                fclose(fp);
               
                }
            posted @ 2012-06-03 23:55 一葉草 閱讀(910) | 評論 (0)編輯 收藏
            2012年5月26日

            教你優化C語言程序

            一般程序如果要進行優化,通常情況下是指優化程序代碼或程序執行速度。優化代碼和優化速度實際上是一個予盾的統一,一般是優化了代碼的尺寸,就會帶來執行時間的增加,如果優化了程序的執行速度,通常會帶來代碼增加的副作用,很難魚與熊掌兼得,只能在設計時掌握一個平衡點。
               
                一、程序結構的優化
               
                1、表達式
               
                對于一個表達式中各種運算執行的優先順序不太明確或容易混淆的地方,應當采用圓括號明確指定它們的優先順序。一個表達式通常不能寫得太復雜,如果表達式太復雜,時間久了以后,自己也不容易看得懂,不利于以后的維護。
               
                2、程序的書寫結構
               
                雖然書寫格式并不會影響生成的代碼質量,但是在實際編寫程序時還是應該尊循一定的書寫規則,一個書寫清晰、明了的程序,有利于以后的維護。在書寫程序時,特別是對于While、for、do…while、if…elst、switch…case等語句或這些語句嵌套組合時,應采用"縮格"的書寫形式,
               
                3、減少判斷語句
               
                能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if語句,有利于減少編譯生成的代碼的長度,能夠不用判斷語句則少用判斷用語句。
               
                4、標識符
               
                程序中使用的用戶標識符除要遵循標識符的命名規則以外,一般不要用代數符號(如a、b、x1、y1)作為變量名,應選取具有相關含義的英文單詞(或縮寫)或漢語拼音作為標識符,以增加程序的可讀性,如:count、number1、red、work等。
               
                5、定義常數
               
                在程序化設計過程中,對于經常使用的一些常數,如果將它直接寫到程序中去,一旦常數的數值發生變化,就必須逐個找出程序中所有的常數,并逐一進行修改,這樣必然會降低程序的可維護性。因此,應盡量當采用預處理命令方式來定義常數,而且還可以避免輸入錯誤。
               
                二、代碼的優化
               
                1、使用自加、自減指令
               
                通常使用自加、自減指令和復合賦值表達式(如a-=1及a+=1等)都能夠生成高質量的程序代碼,編譯器通常都能夠生成inc和dec之類的指令,而使用a=a+1或a=a-1之類的指令,有很多C編譯器都會生成二到三個字節的指令。在AVR單片適用的ICCAVR、GCCAVR、IAR等C編譯器以上幾種書寫方式生成的代碼是一樣的,也能夠生成高質量的inc和dec之類的的代碼。
               
                2、查表
               
                在程序中一般不進行非常復雜的運算,如浮點數的乘除及開方等,以及一些復雜的數學模型的插補運算,對這些即消耗時間又消費資源的運算,應盡量使用查表的方式,并且將數據表置于程序存儲區。如果直接生成所需的表比較困難,也盡量在啟動時先計算,然后在數據存儲器中生成所需的表,后以在程序運行直接查表就可以了,減少了程序執行過程中重復計算的工作量。
               
                3、使用盡量小的數據類型
               
                能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就不要用長整型(long int),能不使用浮點型(float)變量就不要使用浮點型變量。當然,在定義變量后不要超過變量的作用范圍,如果超過變量的范圍賦值,C編譯器并不報錯,但程序運行結果卻錯了,而且這樣的錯誤很難發現。在ICCAVR中,可以在Options中設定使用printf參數,盡量使用基本型參數(%c、%d、%x、%X、%u和%s格式說明符),少用長整型參數(%ld、%lu、%lx和%lX格式說明符),至于浮點型的參數(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不變的情況下,使用%f參數,會使生成的代碼的數量增加很多,執行速度降低。
               
                4、選擇合適的算法和數據結構
               
                應該熟悉算法語言,知道各種算法的優缺點,具體資料請參見相應的參考資料,有很多計算機書籍上都有介紹。將比較慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序執行的效率選擇一種合適的數據結構也很重要,比如你在一堆隨機存放的數中使用了大量的插入和刪除指令,那使用鏈表要快得多。數組與指針語句具有十分密碼的關系,一般來說,指針比較靈活簡潔,而數組則比較直觀,容易理解。對于大部分的編譯器,使用指針比使用數組生成的代碼更短,執行效率更高。但是在Keil中則相反,使用數組比使用的指針生成的代碼更短。
            posted @ 2012-05-26 22:15 一葉草 閱讀(2216) | 評論 (1)編輯 收藏
            2012年4月8日

            c語言中swap問題小結

            #include<stdlib.h>

                #include<stdio.h>

                void swap1(int x,int y)

                {

                int temp;

                temp=x;

                x=y;

                y=temp;

                }

                void swap2(int *x,int *y)

                {

                int *temp;

                temp=x;

                x=y;

                y=temp;

                }

                void swap3(int *x,int *y)

                {

                int temp;

                temp=*x;

                *x=*y;

                *y=temp;

                }

                void swap4(int a[],int b[])

                {

                int temp;

                temp=a[0];

                a[0]=b[0];

                b[0]=temp;

                }

                void swap5(int a[],int b[])

                {

                int temp;

                temp=*a;

                *a=*b;

                *b=temp;

                }

                int main()

                {

                int x,y;

                x=4;

                y=3;

                swap1(x,y);

                printf("swap1: x:%d,y:%d\n",x,y);//形參傳值,不能交換,實際傳過去是拷貝的一份,沒改變主函數中x,y

                swap2(&x,&y);

                printf("swap2: x:%d,y:%d\n",x,y);//不能交換,函數中只是地址交換了下,地址指向的內容沒有交換

                swap3(&x,&y);

                printf("swap3: x:%d,y:%d\n",x,y);//能交換,地址指向的內容進行了交換

                swap4(&x,&y);

                printf("swap4: x:%d,y:%d\n",x,y);//能交換,地址指向的內容進行交換

                swap5(&x,&y);

                printf("swap5: x:%d,y:%d\n",x,y);//能交換,地址指向的內容進行交換

                return 0;

                }

                swap1: x:4,y:3

                swap2: x:4,y:3

                swap3: x:3,y:4

                swap4: x:4,y:3

                swap5: x:3,y:4

            posted @ 2012-04-08 14:15 一葉草 閱讀(1965) | 評論 (0)編輯 收藏
            2012年3月28日

            C/C++ 通用 Makefile

            本文提供了一個用于對 C/C++ 程序進行編譯和連接以產生可執行程序的通用 Makefile.

              在使用 Makefile 之前,只需對它進行一些簡單的設置即可;而且一經設置,即使以后對源程序文件有所增減一般也不再需要改動 Makefile.因此,即便是一個沒有學習過 Makefile 書寫規則的人,也可以為自己的 C/C++ 程序快速建立一個可工作的 Makefile.

              這個 Makefile 可以在 GNU Make 和 GCC 編譯器下正常工作。但是不能保證對于其它版本的 Make 和編譯器也能正常工作。

              如果你發現了本文中的錯誤,或者對本文有什么感想或建議,可通過 whyglinux AT hotmail DOT com 郵箱和作者聯系。

              此 Makefile 的使用方法如下:[list=1][*]程序目錄的組織盡量將自己的源程序集中在一個目錄中,并且把 Makefile 和源程序放在一起,這樣用起來比較方便。當然,也可以將源程序分類存放在不同的目錄中。

              在程序目錄中創建一個名為 Makefile 的文本文件,將后面列出的 Makefile 的內容復制到這個文件中。(注意:在復制的過程中,Makfile 中各命令前面的 Tab 字符有可能被轉換成若干個空格。這種情況下需要把 Makefile 命令前面的這些空格替換為一個 Tab.)

              將當前工作目錄切換到 Makefile 所在的目錄。目前,這個 Makefile 只支持在當前目錄中的調用,不支持當前目錄和 Makefile 所在的路徑不是同一目錄的情況。

              [*]指定可執行文件程序編譯和連接成功后產生的可執行文件在 Makefile 中的 PROGRAM 變量中設定。這一項不能為空。為自己程序的可執行文件起一個有意義的名子吧。

              [*]指定源程序要編譯的源程序由其所在的路徑和文件的擴展名兩項來確定。由于頭文件是通過包含來使用的,所以在這里說的源程序不應包含頭文件。

              程序所在的路徑在 SRCDIRS 中設定。如果源程序分布在不同的目錄中,那么需要在 SRCDIRS 中一一指定,并且路徑名之間用空格分隔。

              在 SRCEXTS 中指定程序中使用的文件類型。C/C++ 程序的擴展名一般有比較固定的幾種形式:。c、。C、。cc、。cpp、。CPP、。c++、。cp、或者。cxx(參見 man gcc)。擴展名決定了程序是 C 還是 C++ 程序:。c 是 C 程序,其它擴展名表示 C++ 程序。一般固定使用其中的一種擴展名即可。但是也有可能需要使用多種擴展名,這可以在 SOURCE_EXT 中一一指定,各個擴展名之間用空格分隔。

              雖然并不常用,但是 C 程序也可以被作為 C++ 程序編譯。這可以通過在 Makefile 中設置 CC = $(CXX) 和 CFLAGS = $(CXXFLAGS) 兩項即可實現。

              這個 Makefile 支持 C、C++ 以及 C/C++ 混合三種編譯方式:[list][*]如果只指定 .c 擴展名,那么這是一個 C 程序,用 $(CC) 表示的編譯命令進行編譯和連接。

              [*]如果指定的是除 .c 之外的其它擴展名(如 .cc、。cpp、。cxx 等),那么這是一個 C++ 程序,用 $(CXX) 進行編譯和連接。

              [*]如果既指定了 .c,又指定了其它 C++ 擴展名,那么這是 C/C++ 混合程序,將用 $(CC) 編譯其中的 C 程序,用 $(CXX) 編譯其中的 C++ 程序,最后再用 $(CXX) 連接程序。

              [/list]這些工作都是 make 根據在 Makefile 中提供的程序文件類型(擴展名)自動判斷進行的,不需要用戶干預。

              [*]指定編譯選項編譯選項由三部分組成:預處理選項、編譯選項以及連接選項,分別由 CPPFLAGS、CFLAGS與CXXFLAGS、LDFLAGS 指定。

              CPPFLAGS 選項可參考 C 預處理命令 cpp 的說明,但是注意不能包含 -M 以及和 -M 有關的選項。如果是 C/C++ 混合編程,也可以在這里設置 C/C++ 的一些共同的編譯選項。

              CFLAGS 和 CXXFLAGS 兩個變量通常用來指定編譯選項。前者僅僅用于指定 C 程序的編譯選項,后者僅僅用于指定 C++ 程序的編譯選項。其實也可以在兩個變量中指定一些預處理選項(即一些本來應該放在 CPPFLAGS 中的選項),和 CPPFLAGS 并沒有明確的界限。

              連接選項在 LDFLAGS 中指定。如果只使用 C/C++ 標準庫,一般沒有必要設置。如果使用了非標準庫,應該在這里指定連接需要的選項,如庫所在的路徑、庫名以及其它聯接選項。

              現在的庫一般都提供了一個相應的 .pc 文件來記錄使用庫所需要的預編譯選項、編譯選項和連接選項等信息,通過 pkg-config 可以動態提取這些選項。與由用戶顯式指定各個選項相比,使用 pkg-config 來訪問庫提供的選項更方便、更具通用性。在后面可以看到一個 GTK+ 程序的例子,其編譯和連接選項的指定就是用 pkg-config 實現的。

              [*]編譯和連接上面的各項設置好之后保存 Makefile 文件。執行 make 命令,程序就開始編譯了。

              命令 make 會根據 Makefile 中設置好的路徑和文件類型搜索源程序文件,然后根據文件的類型調用相應的編譯命令、使用相應的編譯選項對程序進行編譯。

              編譯成功之后程序的連接會自動進行。如果沒有錯誤的話最終會產生程序的可執行文件。

              注意:在對程序編譯之后,會產生和源程序文件一一對應的 .d 文件。這是表示依賴關系的文件,通過它們 make 決定在源程序文件變動之后要進行哪些更新。為每一個源程序文件建立相應的 .d 文件這也是 GNU Make 推薦的方式。

              [*]Makefile 目標(Targets)

              下面是關于這個 Makefile 提供的目標以及它所完成的功能:[list][*]make編譯和連接程序。相當于 make all. [*]make objs僅僅編譯程序產生 .o 目標文件,不進行連接(一般很少單獨使用)。

              [*]make clean刪除編譯產生的目標文件和依賴文件。

              [*]make cleanall刪除目標文件、依賴文件以及可執行文件。

              [*]make rebuild重新編譯和連接程序。相當于 make clean && make all. [/list][/list]關于這個 Makefile 的實現原理不準備詳細解釋了。如果有興趣的話,可參考文末列出的“參考資料”。

              Makefile 的內容如下:############################################################################### # # Generic Makefile for C/C++ Program # # Author: whyglinux (whyglinux AT hotmail DOT com) # Date:   2006/03/04 # Description: # The makefile searches in <SRCDIRS> directories for the source files # with extensions specified in <SOURCE_EXT>, then compiles the sources # and finally produces the <PROGRAM>, the executable file, by linking # the objectives. # Usage: #   $ make           compile and link the program. #   $ make objs      compile only (no linking. Rarely used)。 #   $ make clean     clean the objectives and dependencies. #   $ make cleanall  clean the objectives, dependencies and executable. #   $ make rebuild   rebuild the program. The same as make clean && make all. #============================================================================== ## Customizing Section: adjust the following if necessary. ##============================================================================= # The executable file name. # It must be specified. # PROGRAM   := a.out    # the executable name PROGRAM   := # The directories in which source files reside. # At least one path should be specified. # SRCDIRS   := .        # current directory SRCDIRS   := # The source file types (headers excluded)。 # At least one type should be specified. # The valid suffixes are among of .c, .C, .cc, .cpp, .CPP, .c++, .cp, or .cxx. # SRCEXTS   := .c      # C program # SRCEXTS   := .cpp    # C++ program # SRCEXTS   := .c .cpp # C/C++ program SRCEXTS   := # The flags used by the cpp (man cpp for more)。 # CPPFLAGS  := -Wall -Werror # show all warnings and take them as errors CPPFLAGS  := # The compiling flags used only for C. # If it is a C++ program, no need to set these flags. # If it is a C and C++ merging program, set these flags for the C parts. CFLAGS    := CFLAGS    += # The compiling flags used only for C++. # If it is a C program, no need to set these flags. # If it is a C and C++ merging program, set these flags for the C++ parts. CXXFLAGS  := CXXFLAGS  += # The library and the link options ( C and C++ common)。 LDFLAGS   := LDFLAGS   += ## Implict Section: change the following only when necessary. ##============================================================================= # The C program compiler. Uncomment it to specify yours explicitly. #CC      = gcc # The C++ program compiler. Uncomment it to specify yours explicitly. #CXX     = g++ # Uncomment the 2 lines to compile C programs as C++ ones. #CC      = $(CXX) #CFLAGS  = $(CXXFLAGS) # The command used to delete file. #RM        = rm -f ## Stable Section: usually no need to be changed. But you can add more. ##============================================================================= SHELL   = /bin/sh SOURCES = $(foreach d,$(SRCDIRS),$(wildcard $(addprefix $(d)/*,$(SRCEXTS)))) OBJS    = $(foreach x,$(SRCEXTS), \       $(patsubst %$(x),%.o,$(filter %$(x),$(SOURCES)))) DEPS    = $(patsubst %.o,%.d,$(OBJS)) .PHONY : all objs clean cleanall rebuild all : $(PROGRAM) # Rules for creating the dependency files (。d)。 #—— %.d : %.c @$(CC) -MM -MD $(CFLAGS) $< %.d : %.C @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cc @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cpp @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.CPP @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.c++ @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cp @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cxx @$(CC) -MM -MD $(CXXFLAGS) $< # Rules for producing the objects. #—— objs : $(OBJS) %.o : %.c $(CC) -c $(CPPFLAGS) $(CFLAGS) $< %.o : %.C $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cc $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cpp $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.CPP $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.c++ $(CXX -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cp $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cxx $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< # Rules for producing the executable. #—— $(PROGRAM) : $(OBJS) ifeq ($(strip $(SRCEXTS)), .c)  # C file $(CC) -o $(PROGRAM) $(OBJS) $(LDFLAGS) else                            # C++ file $(CXX) -o $(PROGRAM) $(OBJS) $(LDFLAGS) endif -include $(DEPS) rebuild: clean all clean : @$(RM) *.o *.d cleanall: clean @$(RM) $(PROGRAM) $(PROGRAM)。exe ### End of the Makefile ##  Suggestions are welcome  ## All rights reserved ### ###############################################################################

              下面提供兩個例子來具體說明上面 Makefile 的用法。

              [color=darkred]例一 Hello World 程序[/color]

              這個程序的功能是輸出 Hello, world! 這樣一行文字。由 hello.h、hello.c、main.cxx 三個文件組成。前兩個文件是 C 程序,后一個是 C++ 程序,因此這是一個 C 和 C++ 混編程序。

              /* File name: hello.h  * C header file  */ #ifndef HELLO_H #define HELLO_H #ifdef __cplusplus extern "C" { #endif   void print_hello(); #ifdef __cplusplus } #endif #endif

              /* File name: hello.c  * C source file.  */ #include "hello.h" #include <stdio.h> void print_hello() {   puts( "Hello, world!" ); }

              /* File name: main.cxx  * C++ source file.  */ #include "hello.h" int main() {   print_hello();   return 0; }

              建立一個新的目錄,然后把這三個文件拷貝到目錄中,也把 Makefile 文件拷貝到目錄中。之后,對 Makefile 的相關項目進行如下設置:PROGRAM   := hello      # 設置運行程序名 SRCDIRS   := .          # 源程序位于當前目錄下 SRCEXTS   := .c .cxx    # 源程序文件有 .c 和 .cxx 兩種類型 CFLAGS    := -g         # 為 C 目標程序包含 GDB 可用的調試信息 CXXFLAGS  := -g         # 為 C++ 目標程序包含 GDB 可用的調試信息

              由于這個簡單的程序只使用了 C 標準庫的函數(puts),所以對于 CFLAGS 和 CXXFLAGS 沒有過多的要求,LDFLAGS 和 CPPFLAGS 選項也無需設置。

              經過上面的設置之后,執行 make 命令就可以編譯程序了。如果沒有錯誤出現的話,。/hello  就可以運行程序了。

              如果修改了源程序的話,可以看到只有和修改有關的源文件被編譯。也可以再為程序添加新的源文件,只要它們的擴展名是已經在 Makefile 中設置過的,那么就沒有必要修改 Makefile.

              [color=darkred]例二 GTK+ 版 Hello World 程序[/color]

              這個 GTK+ 2.0 版的 Hello World 程序可以從下面的網址上得到:http://www.gtk.org/tutorial/c58.html#SEC-HELLOWORLD.當然,要編譯 GTK+ 程序,還需要你的系統上已經安裝好了 GTK+.

              跟第一個例子一樣,單獨創建一個新的目錄,把上面網頁中提供的程序保存為 main.c 文件。對 Makefile 做如下設置:PROGRAM   := hello      # 設置運行程序名 SRCDIRS   := .          # 源程序位于當前目錄下 SRCEXTS   := .c         # 源程序文件只有 .c 一種類型 CFLAGS    := `pkg-config ——cflags gtk+-2.0`  # CFLAGS LDFLAGS   := `pkg-config ——libs gtk+-2.0`    # LDFLAGS

              這是一個 C 程序,所以 CXXFLAGS 沒有必要設置——即使被設置了也不會被使用。

              編譯和連接 GTK+ 庫所需要的 CFLAGS 和 LDFLAGS 由 pkg-config 程序自動產生。

              現在就可以運行 make 命令編譯、。/hello 執行這個 GTK+ 程序了。

            posted @ 2012-03-28 19:17 一葉草 閱讀(517) | 評論 (0)編輯 收藏
            2012年3月20日

            C++多繼承中的二義性

            多繼承可以看作是單繼承的擴展。所謂多繼承是指派生類具有多個基類,派生類與每個基類之間的關系仍可看作是一個單繼承。

                多繼承下派生類的定義格式如下:

                class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,…

                {

                <派生類類體>

                };

                其中,<繼承方式1>,<繼承方式2>,…是三種繼承方式:public、private、protected之一。例如:

                class A

                {

                …

                };

                class B

                {

                …

                };

                class C : public A, public B

                {

                …

                };

                其中,派生類C具有兩個基類(類A和類B),因此,類C是多繼承的。按照繼承的規定,派生類C的成員包含了基類A, B中成員以及該類本身的成員。

                多繼承的構造函數

                在多繼承的情況下,派生類的構造函數格式如下:

                <派生類名>(<總參數表>):<基類名1>(<參數表1>),<基類名2>(<參數表2>),…

                <子對象名>(<參數表n+1>),…

                {

                <派生類構造函數體>

                }

                其中,<總參數表>中各個參數包含了其后的各個分參數表。

                多繼承下派生類的構造函數與單繼承下派生類構造函數相似,它必須同時負責該派生類所有基類構造函數的調用。同時,派生類的參數個數必須包含完成所有基類初始化所需的參數個數。

                派生類構造函數執行順序是先執行所屬基類的構造函數,再執行派生類本身構造函數,處于同一層次的各基類構造函數的執行順序取決于定義派生類時所指定的各基類順序,與派生類構造函數中所定義的成員初始化列表的各項順序無關。也就是說,執行基類構造函數的順序取決于定義派生類時基類的順序。可見,派生類構造函數的成員初始化列表中各項順序可以任意地排列。

                下面通過一個例子來說明派生類構造函數的構成及其執行順序。

                #include <iostream.h>

                class B1

                {

                public:

                B1(int i)

                {

                b1 = i;

                cout《"構造函數 B1."《i《 endl;

                }

                void print()

                {

                cout《"B1.print()"《b1《endl;

                }

                private:

                int b1;

                };

                class B2

                {

                public:

                B2(int i)

                {

                b2 = i;

                cout《"構造函數 B2."《i《 endl;

                }

                void print()

                {

                cout《"B2.print()"《b2《endl;

                }

                private:

                int b2;

                };

                class B3

                {

                public:

                B3(int i)

                {

                b3 = i;

                cout《"構造函數 B3."《i《endl;

                }

                int getb3()

                {

                return b3;

                }

                private:

                int b3;

                };

                class A : public B2, public B1

                {

                public:

                A(int i, int j, int k, int l):B1(i), B2(j), bb(k)

                {

                a = l;

                cout《"構造函數 A."《a《endl;

                }

                void print()

                {

                B1::print();

                B2::print();

                cout《"A.print()"《a《","《bb.getb3()《endl;

                }

                private:

                int a;

                B3 bb;

                };

                void main()

                {

                A aa(1, 2, 3, 4);

                aa.print();

                }


            該程序的輸出結果為:

                構造函數 B2.2

                構造函數 B1.1

                構造函數 B3.3

                構造函數 A.4

                B1.print()。1

                B2.print()2

                A.print()4, 3

                在該程序中,作用域運算符::用于解決作用域沖突的問題。在派生類A中的print()函數的定義中,使用了B1::print;和B2::print();語句分別指明調用哪一個類中的print()函數,這種用法應該學會。

                二義性問題

                一般說來,在派生類中對基類成員的訪問應該是唯一的,但是,由于多繼承情況下,可能造成對基類中某成員的訪問出現了不唯一的情況,則稱為對基類成員訪問的二義性問題。

                實際上,在上例已經出現過這一問題,回憶一下上例中,派生類A的兩基類B1和B2中都有一個成員函數print()。如果在派生類中訪問 print()函數,到底是哪一個基類的呢?于是出現了二義性。但是在上例中解決了這個問題,其辦法是通過作用域運算符::進行了限定。如果不加以限定,則會出現二義性問題。

                下面再舉一個簡單的例子,對二義性問題進行深入討論。例如:

                class A

                {

                public:

                void f();

                };

                class B

                {

                public:

                void f();

                void g();

                };

                class C : public A, public B

                {

                public:

                void g();

                void h();

                };

                如果定義一個類C的對象c1:

                C c1;

                則對函數f()的訪問

                c1.f();

                便具有二義性:是訪問類A中的f(),還是訪問類B中的f()呢?

                解決的方法可用前面用過的成員名限定法來消除二義性,例如:

                c1.A::f();

                或者

                c1.B::f();

                但是,最好的解決辦法是在類C中定義一個同名成員f(),類C中的f()再根據需要來決定調用A::f(),還是B::f(),還是兩者皆有,這樣,c1.f()將調用C::f()。

                同樣地,類C中成員函數調用f()也會出現二義性問題。例如:

                viod C::h()

                {

                f();

                }

                這里有二義性問題,該函數應修改為:

                void C::h()

                {

                A::f();

                }

                或者

                void C::h()

                {

                B::f();

                }

                或者

                void C::f()

                {

                A::f();

                B::f();

                }

                另外,在前例中,類B中有一個成員函數g(),類C中也有一個成員函數g()。這時,

                c1.g();

                不存在二義性,它是指C::g(),而不是指B::g()。因為這兩個g()函數,一個出現在基類B,一個出現在派生類C,規定派生類的成員將支配基類中的同名成員。因此,上例中類C中的g()支配類B中的g(),不存在二義性,可選擇支配者的那個名字。

                當一個派生類從多個基類派生類,而這些基類又有一個共同的基類,則對該基類中說明的成員進行訪問時,也可能會出現二義性。例如:

                class A

                {

                public:

                int a;

                };

                class B1 : public A

                {

                private:

                int b1;

                };

                class B2 : public A

                {

                private:

                int b2;

                };

                class C : public B1, public B2

                {

                public:

                int f();

                private:

                int c;

                };

                已知:C c1;

                下面的兩個訪問都有二義性:

                c1.a;

                c1.A::a;

                而下面的兩個訪問是正確的:

                c1.B1::a;

                c1.B2::a;

                類C的成員函數f()用如下定義可以消除二義性:

                int C::f()

                {

                retrun B1::a + B2::a;

                }

                由于二義性的原因,一個類不可以從同一個類中直接繼承一次以上,例如:

                class A : public B, public B

                {

                …

                }

                這是錯誤的。

            posted @ 2012-03-20 22:31 一葉草 閱讀(529) | 評論 (0)編輯 收藏
            2012年3月18日

            將N個實數由大到小排序

            呵呵,最近幾天我有個小發現,那就是老白沒有來看過我的博客了,說真的蠻希望他能來的,
            他不來有點讓我失望,畢竟我也關注他很長一段時間了,當然,不管他來不來,我自己的工作還是得繼續下去的嘛中,對不對,這里我將簡單對于將n個實數由大到小排序做個介紹吧。
                n個實數用數組a描述。
                本例提供用選擇排序方法與冒泡排序方法分別實現n個實數由大到小排序的函數。
                算法一:選擇排序。
                選擇排序需反復進行求最大值與交換兩個數這兩種基本操作。
                對a[o]、a[1]、…、a[n一1]由大到小排序:先求所有數的最大值,然后將最大值與a[o]進行交換;再求a[1]~a[n一1]這些數的最大值,然后將最大值與a[1]進行交換;再求a[2]~a[n一1]這些數的最大值,然后將最大值與a[2]進行交換……;最后求a[n一2]與a[n一1]這些數的最大值,然后將最大值與a[n一2]進行交換。如此,經過n一1輪處理完成排序,本文首發中國自學編程網。
                程序如下:
                void sortl(a,n)/*選擇排序函數*/
                float a[];
                int n:
                {int k,i,j;/*k最大值下標,i,j循環控制變量*/
                float t;/*中間變量,用于兩個數的交換*/
                for(i=0;i<n-1;i++)
                {k=i;/*求最大值下標*/
                for(j=i+1}j<n;j++)
                if(a[j]>a[k])k=j
                t=a[i];a[i]一a[k];a[k]=t;/*進行交換*/
                }
                }
                算法二:冒泡排序。
                冒泡排序需反復進行相鄰兩個數的比較與交換兩個數這兩種基本操作。對相鄰的兩個數進行比較時,如果后面的數大于前面的數,將這兩個數進行交換,大的數往前冒。將所有相鄰的兩個安全閥數比較一遍,稱為一輪比較。如果進行一輪比較無交換,本文首發中國自學編程網排序完成。
                有無交換用一標志變量描述,一輪比較用for循環完成,整個排序利用標志變量用條件循環控制。
                程序如下:
                void sort2(a,n)/*冒泡排序函數*/
                float a[];
                int n;
                {int i;/*一輪比較的循環控制變量*/
                int flag;/*標志變量,為1有交換,為0無交換*/
                float t;/*中間變量,用于兩個數的交換*/
                do
                {flag=O;/*先假定無交換,已排好序*/
                for(i=O;i<n一2; i++)
                if(a[i+1]>a[i])
                {t=a[i];a[i]=a[i+1];a[i+1]=t;/*進行交換*/
                flag=1;/*有交換,標志變量的值改變為1*/
                }
                }while(flag==1);
                )
                由小到大排序請讀者作類似考慮。呵呵,差不多了,如果有不當之處,請朋友們指正啊---
            posted @ 2012-03-18 13:31 一葉草 閱讀(673) | 評論 (0)編輯 收藏
            2012年3月11日

            c語言中位段的使用

            位段以位為單位定義結構體(或共用體)中成員所占存儲空間的長度。

                含有位段的結構體類型稱為位段結構。

                位段結構也是一種結構體類型,只不過其中含有以位為單位定義存儲長度的整數類型位段成員。采用位段結構既節省存儲空間,又可方便操作。

                位段結構中位段的定義格式為:

                unsigned <成員名>:<二進制位數>

                例如:

                struct bytedata

                {unsigned a:2;   /*位段a,占2位*/

                unsigned:6;  /*無名位段,占6位,但不能訪問*/

                unsigned:0;     /*無名位段,占0位,表下一位段從下一字邊界開始*/

                unsigned b:10;  /*位段b,占10位*/

                int i;          /*成員i,從下一字邊界開始*/

                }data;

                位段數據的引用:

                同結構體成員中的數據引用一樣,但應注意位段的最大取值范圍不要超出二進制位數定的范圍,否則超出部分會丟棄。

                例如:data.a=2;   但  data.a=10;就超出范圍(a占2位,最大3)

                關于位段數據,注意以下幾點:

                (1)一個位段必須存儲在同一存儲單元(即字)之中,不能跨兩個單元。如果其單元空間不夠,則剩余空間不用,從下一個單元起存放該位段。

                (2)可以通過定義長度為0的位段的方式使下一位段從下一存儲單元開始。

                (3)可以定義無名位段。

                (4)位段的長度不能大于存儲單元的長度。

                (5)位段無地址,不能對位段進行取地址運算。

                (6)位段可以以%d,%o,%x格式輸出。

                (7)位段若出現在表達式中,將被系統自動轉換成整數。

                -------------------------------------------------------

                C語言中用結構實現位段--個人心血!值得一看哦!C語言中的結構是有實現位段的能力的,噢!你問它到底是什么形式是吧?這個問題呆會給你答案。讓我們先看看位段的作用:位段是在字段的聲明后面加一個冒號以及一個表示字段位長的整數來實現的。這種用法又被就叫作“深入邏輯元件的編程”,如果你對系統編程感興趣,那么這篇文章你就不應該錯過!

                我把使用位段的幾個理由告訴大家:1、它能把長度為奇數的數據包裝在一起,從而節省存儲的空間;2、它可以很方便地訪問一個整型值的部分內容。

                首先我要提醒大家注意幾點:1、位段成員只有三種類型:int ,unsigned int 和signed int這三種(當然了,int型位段是不是可以取負數不是我說了算的,因為這是和你的編譯器來決定的。位段,位段,它是用來表示字段位長(bit)的,它只有整型值,不會有7.2這種float類型的,如果你說有,那你就等于承認了有7.2個人這個概念,當然也沒有char這個類型的);2、成員名后面的一個冒號和一個整數,這個整數指定該位段的位長(bit);3、許多編譯器把位段成員的字長限制在一個int的長度范圍之內;4、位段成員在內存的實現是從左到右還是從右到左是由編譯器來決定的,但二者皆對。

                下面我們就來看看,它到底是什么東西(我先假定大家的機器字長為32位):

                Struct WORD

                {

                unsigned int chara: 6:

                unsigned int font : 7;

                unsigned int maxsize : 19;

                };

                Struct WORD chone;

                這一段是從我編寫的一個文字格式化軟件摘下來的,它最多可以容納64(既我說的unsigned int chara :6; 它總共是6位)個不同的字符值,可以處理128(既unsigned int font : 7 ;既2的7次方)種不同的字體,和2的19次方的單位長度的字。大家都可以看到maxsize是19位,它是無法被一個short int 類型的值所容納的,我們又可以看到其余的成員的長度比char還小,這就讓我們想起讓他們共享32位機器字長,這就避免用一個32位的整數來表示maxsize的位段。怎么樣?還要注意的是剛才的那一段代碼在16位字長的機器上是無法實現的,為什么?提醒你一下,看看上面提醒的第3點,你會明白的!

                你是不是發現這個東西沒有用???如果你點頭了,那你就錯了!這么偉大的創造怎么會沒有用呢(你對系統編程不感興趣,相信你會改變這么一個觀點的)?磁盤控制器大家應該知道吧?軟驅與它的通信我們來看看是怎么實現的下面是一個磁盤控制器的寄存器:

                │←5→│←5→│←9→│←8→│←1→│←1→∣←1→∣←1→∣←1→∣

                上面位段從左到右依次代表的含義為:5位的命令,5位的扇區,9位的磁道,8位的錯誤代碼,1位的HEAD LOADED,1位的寫保護,1位的DISK SPINNING,1位的錯誤判斷符,還有1位的READY位。它要怎么來實現呢?你先自己寫寫看:

                struct DISK_FORMAT

                {

                unsigned int command : 5;

                unsigned sector : 5;

                unsigned track : 9 ;

                unsigned err_code : 8;

                unsigned ishead_loaded : 1;

                unsigned iswrit_protect : 1;

                unsigned isdisk_spinning : 1;

                unsigned iserr_ocur : 1;

                undigned isready :1 ;

                };

                注:代碼中除了第一行使用了unsigned int 來聲明位段后就省去了int ,這是可行的,詳見ANCI C標準。

                如果我們要對044c18bfH的地址進行訪問的話,那就這樣:

                #define DISK ((struct DISK_FORMAT *)0x044c18bf)

                DISK->sector=fst_sector;

                DISK->track=fst_track;

                DISK->command=WRITE;

                當然那些都是要宏定義的哦!

                我們用位段來實現這一目的是很方便的,其實這也可以用移位或屏蔽來實現,你嘗試過就知道哪個更方便了!

            posted @ 2012-03-11 17:35 一葉草 閱讀(532) | 評論 (0)編輯 收藏
            2012年2月26日

            C++中使用Expat解析XML

            使用expat的原因很多,主要還是因為expat更靈活。習慣了TinyXML,一開始不太習慣expat,分析一下,其實很容易上手的。

                1.回調函數

                以下案例解析xml文件中的elment,attribute和text。expat使用回調方式返回xml數據,解析器解析到一個element及其內部屬性后,將調用事先設置好的函數,同樣,當element結束和text結束后,也會分別調用對應的函數。

                2.如何處理數據之間的包含關系

                典型的方式是定義三個函數分別處理elment開始(含屬性)、element結束和文本內容?;卣{函數的第一個參數是自定義的,通常用于存儲 XML文檔的上下文信息,用XML_SetUserData可以設置這個參數,下例中傳遞一個整數指針,以便在每次回調時能知道該元素是第幾層元素。

                該參數也可以是一個棧對象的地址,開始一個元素時,將新元素對應的數據壓入堆棧,處理下一級元素時,新元素是棧頂元素在子元素,然后處理完了繼續把該元素壓入堆棧,繼續下一級新的子元素。當元素結束后,需要出棧,以便解析下個兄弟元素程時能取到父節點。

                好啦,基本應用還是很簡單的,實際上Expat的API函數不多。

                3.如何處理屬性

                屬性通過ElementHandler回調函數傳入,這里有一個char** atts就是屬性,這是一個字符指針數組,如果有N個屬性,數組大小就是2*N+1,最后一個素組元素為空指針,奇數指針對應屬性名稱,偶數指針對應屬性值(字符串格式)??梢栽谝粋€循環中處理多個屬性,當遇到空指針時,表示沒有更多屬性了。

                好啦,先看sample吧:

                #include <stdio.h>

                #include "expat.h"

                #pragma warning(disable:4996)

                #define XML_FMT_INT_MOD "l"

                static void XMLCALL startElement(void *userData, const char *name, const char **atts)

                {

                int i;

                int *depthPtr = (int *)userData;

                for?。╥ = 0; i < *depthPtr; i++)

                printf(" ");

                printf(name);

                *depthPtr += 1;

                for(i=0;atts[i]!=0;i+=2)

                {

                printf(" %s=%s",atts[i],atts[i+1]);

                }

                printf("\n");

                }

                static void XMLCALL endElement(void *userData, const char *name)

                {

                int *depthPtr =?。╥nt *)userData;

                *depthPtr -= 1;

                }

                int main(int argc, char *argv[])

                {

                char buf[BUFSIZ];  XML_Parser parser = XML_ParserCreate(NULL);

                int done;  int depth = 0;

                XML_SetUserData(parser, &depth);

                XML_SetElementHandler(parser, startElement, endElement);

                FILE* pFile= argc<2 ?stdin : fopen(argv[1],"rb");

                do

                {    int len =?。╥nt)fread(buf, 1, sizeof(buf), pFile);

                done = len < sizeof(buf);

                if?。╔ML_Parse(parser, buf, len, done) == XML_STATUS_ERROR)

                {

                fprintf(stderr,"%s at line %" XML_FMT_INT_MOD "u\n",

                XML_ErrorString(XML_GetErrorCode(parser)),

                XML_GetCurrentLineNumber(parser));

                return 1;

                }

                }

                while?。?!done);

                XML_ParserFree(parser);

                fclose(pFile);

                return 0;

                }

                4.其他ElementHanlder

                expat還可以設置CData,Comment的handler,另外一些函數本人還沒使用過,涉及到更多的xml標準的知識,如果需要,可以參考官方的手冊。

            posted @ 2012-02-26 13:31 一葉草 閱讀(2041) | 評論 (0)編輯 收藏
            2012年2月22日

            c++ 處理Json

            一、摘要

            JSON 的全稱為:JavaScript Object Notation,顧名思義,JSON 是用于標記 Javascript 對象的,JSON 官方的解釋為:JSON 是一種輕量級的數據傳輸格式。

            本文并不詳細介紹 JSON 本身的細節,旨在討論如何使用 C++ 語言來處理 JSON。關于 JSON 更具體的信息,可參見 JSON 官網:http://www.json.org。

            二、本文選擇處理 JSON C++

            本文選擇一個第三方庫 jsoncpp 來解析 JSON。jsoncpp 是比較出名的 C++ JSON 解析庫。在 JSON 官網也是首推的。

            下載地址為:http://sourceforge.net/projects/jsoncpp。本文使用的 jsoncpp 版本為:0.5.0。

            三、jsoncpp Windows 下的編譯

            要使用第三方源碼庫,第一步少不了的就是編譯,將源碼文件編譯成我們方便使用的動態鏈接庫、靜態鏈接庫或者靜態導入庫[1]。

            jsconcpp 進行 JSON 解析的源碼文件分布在 include/json、src/lib_json 下。其實 jsoncpp 源碼并不多,為了方便產品管理,此處沒必要將其編譯為動態鏈接庫或者靜態導入庫,所以我們選擇使用靜態鏈接庫[2]。

            jsoncpp 已經處理的很完善了,所有編譯選項都已經配置好,打開makefiles/vs71/jsoncpp.sln 便可以開始編譯(默認是使用 VS2003 編譯器的,打開時直接按照 VS2005 提示轉換即可)。

            四、jsoncpp 使用詳解

            jsoncpp 主要包含三種類型的 class:Value、Reader、Writer。jsoncpp 中所有對象、類名都在 namespace Json 中,包含 json.h 即可。

            Json::Value 只能處理 ANSI 類型的字符串,如果 C++ 程序是用 Unicode 編碼的,最好加一個 Adapt 類來適配。

            1、Value

            Json::Value 是jsoncpp 中最基本、最重要的類,用于表示各種類型的對象,jsoncpp 支持的對象類型可見 Json::ValueType 枚舉值。

            可如下是用 Json::Value 類:

            Json::Value json_temp; // 臨時對象,供如下代碼使用

            json_temp["name"] = Json::Value("huchao");

            json_temp["age"] = Json::Value(26);

            Json::Value root; // 表示整個 json 對象

            root["key_string"] = Json::Value("value_string"); // 新建一個 Key(名為:key_string),賦予字符串值:"value_string"。

            root["key_number"] = Json::Value(12345); // 新建一個 Key(名為:key_number),賦予數值:12345。

            root["key_boolean"] = Json::Value(false); // 新建一個 Key(名為:key_boolean),賦予bool值:false。

            root["key_double"] = Json::Value(12.345); // 新建一個 Key(名為:key_double),賦予 double 值:12.345。

            root["key_object"] = Json_temp; // 新建一個 Key(名為:key_object),賦予 json::Value 對象值。

            root["key_array"].append("array_string"); // 新建一個 Key(名為:key_array),類型為數組,對第一個元素賦值為字符串:"array_string"。

            root["key_array"].append(1234); // 為數組 key_array 賦值,對第二個元素賦值為:1234。

            Json::ValueType type = root.type(); // 獲得 root 的類型,此處為 objectValue 類型。

            注:跟C++ 不同,JavaScript 數組可以為任意類型的值,所以 jsoncpp 也可以。

            如上幾個用法已經可以滿足絕大部分 json 應用了,當然 jsoncpp 還有一些其他同能,比如說設置注釋、比較 json 大小、交換 json 對象等,都很容易使用,大家自己嘗試吧。

            2Writer

            如上說了 Json::Value 的使用方式,現在到了該查看剛才賦值內容的時候了,查看 json 內容,使用 Writer 類即可。

            Jsoncpp 的 Json::Writer 類是一個純虛類,并不能直接使用。在此我們使用 Json::Writer 的子類:Json::FastWriter、Json::StyledWriter、Json::StyledStreamWriter。

            顧名思義,用 Json::FastWriter 來處理 json 應該是最快的,下面我們來試試。

            Json::FastWriter fast_writer;

            std::cout << fast_writer.write(root) << std::endl;

            輸出結果為:

            {"key_array":["array_string",1234],"key_boolean":false,"key_double":12.3450,"key_number":12345,"key_object":{"age":26,"name":"huchao"},"key_string":"value_string"}

            再次顧名思義,用 Json::StyledWriter 是格式化后的 json,下面我們來看看 Json::StyledWriter 是怎樣格式化的。

            Json::StyledWriter styled_writer;

            std::cout << styled_writer.write(root) << std::endl;

            輸出結果為:

            {

            "key_array" : [ "array_string", 1234 ],

            "key_boolean" : false,

            "key_double" : 12.3450,

            "key_number" : 12345,

            "key_object" : {

            "age" : 26,

            "name" : "huchao"

            },

            "key_string" : "value_string"

            }

            3、Reader

            Json::Reader 是用于讀取的,說的確切點,是用于將字符串轉換為 Json::Value 對象的,下面我們來看個簡單的例子。

            Json::Reader reader;

            Json::Value json_object;

            const char* json_document = "{\"age\" : 26,\"name\" : \"huchao\"}";

            if (!reader.parse(json_document, json_object))

            return 0;

            std::cout << json_object["name"] << std::endl;

            std::cout << json_object["age"] << std::endl;

            輸出結果為:

            "huchao"

            26

            可見,上述代碼已經解析出了 json 字符串。

            posted @ 2012-02-22 12:31 一葉草 閱讀(2190) | 評論 (0)編輯 收藏
            2012年2月17日

            論C/C++函數間動態內存的傳遞

            當你涉及到C/C++的核心編程的時候,你會無止境地與內存管理打交道。這些往往會使人受盡折磨。所以如果你想深入C/C++編程,你必須靜下心來,好好苦一番。

              現在我們將討論C/C++里我認為哪一本書都沒有完全說清楚,也是涉及概念細節最多,語言中最難的技術之一的動態內存的傳遞。并且在軟件開發中很多專業人員并不能寫出相關的合格的代碼。

              一、引入

              看下面的例子,這是我們在編寫庫函數或者項目內的共同函數經常希望的。

              void MyFunc(char *pReturn, size_t size)

              {………

              pReturn = (char *)malloc(sizeof(char) * num);………

              }我們可以很明顯地看出代碼作者的意圖,他想在函數調用處聲明一個指針 char *pMyReturn=NULL;然后調用MyFunc處理并返回一段長度為size的一段動態內存。

              那么作者能達到預期的效果嗎?

              那么我可以告訴作者,他的程序在編譯期很幸運地通過了,可是在運行期他的程序崩潰終止。原因何在,是他觸犯了系統不可侵犯的條款:錯誤地操作內存。

              二、內存操作及問題相關知識點

              為了能徹底解決動態內存傳遞的問題,我們先回顧一下內存管理的知識要點。

             ?。?)內存分配方式有三種:

              從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。

              在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。

              從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活。

             ?。?)指針的操作流程

              申請并初始化或設置為空:

              int *pInt=NULL;開辟空間或者使其指向對象:

              pInt=new Int(3);或者int i=3;pint=&i;用指針(更確切地說是操作內存,在使用之前加if(pint!=NULL)或者assert(pInt!=NULL)后再使用,以防內存申請失敗的情況下使用指針):

              if(p!=NULL) {use pint};釋放使用完的內存

              free(pInt);置指針為空

              pInt=NULL;(避免野指針的出現)

              (3)在函數的參數傳遞中,編譯器總是要為函數的每個參數制作臨時副本,如果參數為p的話,那么編譯器會產生p的副本_p,使_p=p; 如果函數體內的程序修改了_p的內容,就導致參數p的內容作相應的修改。這就是指針可以用作輸出參數的原因。

              三、問題分析

              根據上面的規則我們可以很容易分析例子中失敗的原因。

              void MyFunc(char *pReturn, size_t size)

              {………

              pReturn = (char *)malloc(sizeof(char) * num);………

              } void main(void){ char *pMyReturn=NULL;MyFunc(pMyReturn,10);}在MyFunc(char *pReturn, size_t size)中_pMyReturn真實地申請到了內存, pMyReturn申請了新的內存,只是把_pMyReturn 所指的內存地址改變了,但是pMyReturn絲毫未變。所以函數MyFunc并不能輸出任何東西。事實上,每執行一次MyFunc就會泄露一塊內存,因為沒有用free釋放內存。

              四、問題解決方案

              函數間傳遞動態數據我們可以有三種解決方法。

              方法一:如果我們是用C++編程,我們可以很方便地利用引用這個技術。我也極力推薦你用引用,因為它會使你少犯一些錯誤。以下是一個例子。

              void MyFunc(char* &pReturn,size_t size){ pReturn=(char*)malloc(size);memset(pReturn,0x00,size);if(size>=13)

              strcpy(pReturn,"Hello World!");}

              void main(){ char *pMyReturn=NULL;MyFunc(pMyReturn,15);if(pMyReturn!=NULL)

              { char *pTemp=pMyReturn;while(*pTemp!=''\0'')

              cout<<*pTemp++;pTemp=NULL;strcpy(pMyReturn,"AAAAAAAA");free(pMyReturn);pMyReturn=NULL;}方法二:利用二級指針

              void MyFunc (char ** pReturn, size_t size)

              { * pReturn = (char *)malloc(size);} void main(void)

              { char * pMyReturn = NULL;MyFunc (&pMyReturn, 100);// 注意參數是 & pMyReturn if(pMyReturn!=NULL){ strcpy(pMyReturn, "hello");cout<< pMyReturn << endl;free(pMyReturn);pMyReturn=NULL;}}為什么二級指針就可以了。原因通過函數傳遞規則可以很容易地分析出來。我們將& pMyReturn傳遞了進去,就是將雙重指針的內容傳遞到了函數中。函數過程利用改變指針的內容,這樣pMyReturn很明顯指向了開辟的內存 .

              方法三:用函數返回值來傳遞動態內存

              char * MyFunc (void)

              { char *p =new char[20];memset(p,0x00,sizeof(p));return p;} void main(void)

              { char *str = NULL;str = MyFunc();if(str!=NULL)

              { strcpy(str,"Hello,baby");cout<< str << endl;free(str);str=NULL;}請注意的是函數寫成這樣的話,你是不能返回什么動態內存的,因為p指向的是字符串常量。內存在位于靜態存儲區上分配,你無法改變。(你想要得到動態內存我們一定要看到malloc或者new)。

              char * MyFunc (void)

              { char *p =“Hello World”

              return p;}結束語

              操作內存是C/C++一個難點,我們作為專業的軟件開發人員。應該深入理解并能靈活地掌握指針和內存的操作。

            posted @ 2012-02-17 12:13 一葉草 閱讀(2515) | 評論 (11)編輯 收藏
            僅列出標題  下一頁
            合区精品久久久中文字幕一区| 国产成人精品白浆久久69| 久久精品天天中文字幕人妻| 久久最新免费视频| 久久精品18| 久久伊人精品青青草原日本| 99国内精品久久久久久久| 久久96国产精品久久久| 久久久久无码精品国产不卡| 无码专区久久综合久中文字幕| 久久久国产精华液| 无码人妻精品一区二区三区久久久 | 国产精品成人久久久久久久| 久久香蕉综合色一综合色88| 久久久久成人精品无码中文字幕| 亚洲国产精品久久电影欧美| 色欲久久久天天天综合网| 亚洲AV日韩精品久久久久久久| 久久精品国产久精国产思思| www.久久99| 久久精品国产精品亚洲人人 | 久久精品国产清自在天天线| 久久久精品日本一区二区三区| 人妻精品久久久久中文字幕| 亚洲午夜久久久久妓女影院| 国内精品久久久久影院一蜜桃| 狠狠色丁香久久综合五月| 久久亚洲国产精品五月天婷| 狠狠色综合网站久久久久久久高清| 国内精品久久久久伊人av| 国内精品久久久久久久涩爱 | 日韩AV毛片精品久久久| 久久亚洲精精品中文字幕| 精品久久久久久久无码| 久久国产精品国语对白| 色综合久久久久综合体桃花网| 精品无码久久久久久久动漫| 午夜精品久久久久久久| 久久国产三级无码一区二区| 新狼窝色AV性久久久久久| 久久综合九色综合欧美就去吻|