C++学习笔记

C++学习笔记

 

   编程设计

     1.将程序划分为多个子系统,包括子系统间的接口和依赖关系、子系统间的数据流、在各子系统间的来回输入输出、以及总的线程模型。

 

     2.各个子系统的具体细节,包括进一步细分的类、类层次体系、数据结构、算法、特定的线程模型和错误处理。

 

   设计流程

     1.需求:功能需求和性能需求。

     2.设计步骤

       (1)把程序划分为通用功能子系统,并明确子系统的接口和交互。

       (2)把这些子系统列在一个表中,并表示出子系统的高层行为或功能、子系统向其它子系统提供的接口,及此子系统使用了其他子系统的哪些接口。

       (3)选择线程模型:确定使用多少个线程,明确线程的交互并为共享数据指定加锁机制。

       (4)为每个子系统指定层次体系

       (5)为每个子系统指定类、数据结构、算法和模式

       (6)为每个子系统指定错误处理(系统错误 + 用户错误),指定是否使用异常

 

   C++ 的设计原则

     1.抽象:将接口与实现相分离

     2.重用:代码重用和思想重用

 

   对象关系

     1.has-a 关系(聚集)

     2.is-a  关系(继承)

     3.组织对象层次体系:

      (1)将类按有意义的功能加以组织。

      (2)将共同的功能放到超类中,从而支持代码重用。

      (3)避免子类过多的覆盖父类的功能。

 

   重用设计

     1.建立可重用的代码结构

      (1)避免将无关或逻辑上分离的概念混在一起

      (2)把程序划分为子系统

      (3)使用类层次体系来分离逻辑概念

      (4)使用聚集来分离逻辑概念

      (5)对通用数据结构和算法使用模板

      (6)提供适当的检查和防护

 

   设计易于使用的接口

     1.开发直观的接口

     2.不要遗漏必要的功能

     3.提供简洁的接口

      (1)消除重复的接口

      (2)只提供所需的功能

      (3)适当的限制库的使用

     4.提供文档和注释

      (1)公共的文档应指定行为,而不是底层实现

 

   设计通用的接口

     1.提供多种方法来完成同一功能

     2.提供定制能力

 

   协调一般性和通用性

     1.提供多个接口

     2.优化常用功能

 

   代码注释

     (1)前缀注释

        * 文件/类名

        * 最后一次修改时间

        * 原作者

        * 文件所实现特性的编号 (特性 ID)

        * 版权信息

        * 文件/类的简要描述

        * 未完成的特性

        * 已知的 bug

 

     (2)注释示例

        /*

         * Watermelon.cpp

         *

         * $Id: Watermelon.cpp,v 1.6 2004/03/10 12:52:33 klep Exp $

         *

         * Implements the basic functionality of a watermelon. All

         * unit are expressed in terms of seeds per cubic centimeter

         * Watermelon theory is based on the white paper "Alogorthms

         * for Watermelon Processing."

         *

         * The following code is (c)copyright 2004. FruitSoft, Inc.

         * All right reserved

         */

 

   编写代码

     1.类定义在 C++ 中是一条语句,因此必须以分号结束。

     2.:: 指作用域解析操作符。

     3.和堆上使用对象的区别

       (1)上创建对象

          SpreadsheetCell myCell, anotherCell;

 

       (2)在堆上使用对象

          SpreadsheetCell *myCellp = new SpreadsheetCell();

 

       (3)如果用 new 来分配一个对象,用完该对象时要用 delete 来释放

 

     4.C++ 程序员通常把构造函数称为 "ctor"

 

     5.使用构造函数

       (1)上使用构造函数

          SpreadsheetCell myCell(5);

 

       (2)在堆上使用构造函数

          SpreadsheetCell *myCell = new SpreadsheetCell(5);

          delete myCell;

 

       (3)不要尝试从类的一个构造函数调用另一个构造函数

 

     6.使用默认构造函数

       (1)上使用构造函数

          SpreadsheetCell myCell;  //right

          SpreadsheetCell myCell();//wrong

 

       (2)上创建对象时,要去掉默认构造函数的小括号

       (3)在堆上使用默认构造函数

          SpreadsheetCell *myCellp = new SpreadsheetCell();

 

       (4)什么时候需要使用构造函数

          SpreadsheetCell cells[3];//fails comilation without default ctor

         SpreadsheetCell *myCellp = new SpreadsheetCell[10];//alse fails

 

       (5)使用初始化列表

          1)初始化列表允许在创建数据成员的同时完成数据成员的初始化

          2)使用初始化列表的情况

 

      数据成员                                         解释

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

      const 数据成员                              必须在创建时提供值

      引用数据成员                               引用无法独立存在

      没默认构造函数的对象成员       对象成员无法初始化

      没有默认构造函数的超类           见后面

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

 

     7.对象的撤销

       (1)析构函数仅用于释放内存或释放其他资源是一个不错的想法

 

     8.浅复制与深复制

       (1)浅复制:只是从源对象直接将数据成员复制或赋值到目标对象

       (2)深复制:非浅复制

       (3)只要在类中动态分配了内存,就应该编写自己的复制构造函数来提供内存的深复制

       (4)在对一个对象进行赋值前,必须先释放此对象的所有动态分配的内存

       (5)只要类会动态分配内存,就需要编写析构函数、复制构造函数、赋值操作符

       (6)禁止对象赋值,可将复制构造函数与赋值操作符声明为私有成员

       (7)不必为私有复制构造函数和赋值操作符提供实现,编译器不要求

 

十一 精通类和对象

     1.不能在静态方法中访问非静态数据成员

     2.保证一个对象不会修改数据成员,可用 const 来标记

 

     3.保证一个方法不会修改数据成员,可用 const 来标记

       (1)在类定义中的声明 double getValue() const;

       (2)在源文件中的实现

          double Spreadsheet::getValue() const

         {

            return this.mValue;

         }

 

     4. const 对象可以调用 const 和非 const 方法,const 对象只能调用const 方法

 

     5.应将所有不会修改对象的方法都声明为 const,并在程序中使用 const对象引用

 

     6.将变量置为 mutable,这样编译器允许在 const 方法中修改这个变量

     7.C++ 不允许仅基于方法的返回类型而重载一个方法名

     8.默认参数:从最右参数开始的连续参数表

     9.只能在方法声明中指定默认参数,在定义中并不指定

     10一个构造函数的所有参数都有默认值,此函数会作为默认构造函数

     11能利用默认参数做到的事情,利用方法重载也可以做,用你最熟悉的

 

     12内联:将方法体或函数体直接插入到代码调用处(相当于 #define 宏的安

       全版本),内联示例如下:

 

       (1)在类的源文件(SpreadsheetCell.cpp)

       inline double SpreadsheetCell::getValue() const

       {

           mNumAccess++;

           return mValue;

       }

 

       (2)或在类的声明文件中直接实现此方法而不用 inline 关键字

       //SpreadsheetCell.h

       double getValue() const (mNumAccesses++; return mValue;}

 

     13友元可以访问指定类中的 protected private 数据成员和方法

       (1)声明友元类

          class SpreadsheetCell

          {

             public:

                 friend class Spreadsheet;

                //code omitted here

          };

 

       (2)声明友元方法

          class SpreadsheetCell

         {

             public:

                 friend bool checkSpreadsheetCell();

                 //code omitted here

         };

 

 

十二 C++ 中的继承机制

     1.超类指针(引用)在引用子类时,了类仍然会保留它们覆盖的方法。而在

       强制类型转换成超类对象时,子类会失去它们的独有特性。覆盖方法和子

       类数据的丢失称为切割。

 

     2.作为一条经验,要把所有的方法都用 virtual 声明 (包括析构函数,但

       是不包括构造函数) 来避免因遗漏关键字 virtual 而产生的相关问题。

 

     3.virtual 用法示例:

       class Sub : public Super

       {

           public:

           Sub();

           virtual void someMethod();

           virtual void someOtherMethod();

       }

 

     4.要把所有的析构函数都用 virtual 声明

 

     5.强制类型转换

       (1)静态转换 static_cast<type>

          示例:

          Sub* mySub = static_cast<Sub*>(inSuper);

 

       (2)动态转换 dynamic_cast<type>

          示例:

          Sub* mySub = dynamic_cast<Sub*>(inSuper);

          if (mySub == NULL)

         {

              //proceed to access sub methods on mySub

         }

  

   注意:如果对指针不能进行动态类型转换,指针则为 NULL, 而不是指

   向无意义的数据。

 

     6.进行向上强制类型转换时,要使用指向超类的指针或引用来避免切割问题。

 

     7.纯虚方法与抽象基类

       (1)纯虚方法: 在类定义中显示未定义的方法。

       (2)抽象类: 含有纯虚方法的类(不能实例化)

       (3)纯虚方法语法定义: 在类定义中简单的设置方法等于 0, cpp 文件

          中不要编写其实现代码。

   示例:

   class SpreadsheetCell

   {

       public:

           SpreadsheetCell();

           virtual ~SpreadsheetCell();

           virtual void set(const std::string instring) = 0;

           virtual std::string getString() const = 0;

     };

 

     8.定制类型转换函数

       (1)double 类型转换成 string 类型

          #include <iostream>

          #include <sstream>

  

          double inValue;

          string myString;

 

          ostringstream ostr;

          ostr << inValue;

          myString = ostr.str();

 

       (2)string 类型转换成 double 类型

          #include <iostream>

          #include <sstream>

  

          double myDouble;

          string inString;

  

          istringStream istr(inString);

          istr >> myDouble;

         if (istr.fail())

        {

           myDouble = 0;

        }

 

     9.使用预编译指令避免重复包含头文件

       #ifndef _TEST_H_

       #define _TEST_H_

       // include header files here      

       // other code omitted here

 

       #endif

  

十三 覆盖方法的特殊情况

     1. C++ 中不能覆盖静态方法。

       (1)不能同时用 virtual static 声明一个方法。

       (2)在对象上可以调用 static 方法,但 static 方法只存在于类中。

 

十四 利用模板编写通用代码

     1.模板相关概念

       (1)类模板: 存储对象的容器或数据结构。

       (2)模板的语法:

          template <typename T>

         class Grid

         {

             public:

                 Grid(int inWidth, int inHeight);

                 Grid(const Grid<T>& src);

                 Grid<T>& operator=(const Grid<T>& rhs);

                 T& getElementAt(int x, int y);

                 const T& getElementAt(int x, int y);

                 void setElementAt(int x, int y, const T& inElem);

 

             protected:

                 void copyFrom(const Grid<T>& src);

                 T** mCells;

        };

        (3)语法解释

            template <typename T> : 指在类型 T 上定义的模板。

            Grid<T> : Grid 实际上是模板名。

            Grid<T> : 将作为类模板中的类名。

 

 (4)模板定义(实现)

    template <typename T>

    Grid<T>::Grid(int inWidth, int inHeight)

    {

        mCells = new T* [mWidth];

        for (int i = 0; i < mWidth; i++)

        {

            mCells[i] = new T[mHeight];

        }

    }

 

 (5)模板实例化

    Grid<int> myIntGrid;

 

十五 C++ 中的一些疑难问题

     1.引用

       (1)定义: 另一个变量的别名, 对引用的修改会改变其所指向的变量。

       (2)引用变量必须在创建时就初始化。

          int  x = 3;   // right

          int& xRef = x;// right

          int& emptyRef;//wrong

          : 类的引用数据成员可在构造函数的初始化列表中初始化。

 

       (3)不能创建指向未命名值的引用(const 常量值除外)

          int& unnameRef = 5;      //does not compile

          const int& unnameRef = 5;//works as expect

 

       (4)修改引用: 引用总是指向初始化时指定的那个变量。

          int x = 3, y = 4;

          int& xRef = x;

          xRef = y; // change x value to 4, doesn't make refer to y;

          xRef = &y; // doesn't compile, type not match

         : 引用指向的变量在初始化之后不能再改变, 只能改变此变量的值

 

       (5)指针引用和引用指针

          //指针引用示例(指向指针的引用)

          int*  intP;

          int*& ptrRef = intP;

          ptrRef = new int;

         *ptrRef = 5;

 

         :不能声明指向引用的引用, 也不能声明引用指针(指向引用的指针)

         int x = 3;

         int& xRef = x;

         int&& xDoubleRef = xRef; // not compile

         int&* refPtr = &xRef; // not compile

  

       (6)传引用vs传值

          1)效率。复制大对象和结构要花费很长时间。

          2)正确性。不是所有的对象都允许传值或正确的支持深复制。

          3)不想修改原对象,又利用以上两优点,可在参数前加 const

         4)对简单内置类型(intdouble)要传值,其它所有情况可传引用。

 

       (7)引用vs指针

          1)引用让程序清晰,易于理解。

          2)引用比指针安全,不存在无效的引用,不需要明确解除引用。

          3)除非需要动态分配内存或在其它地方要改变或释放指针指向的值,否则都应使用引用而非指针。

    

     2.疑难字 const

       (1)const 变量

          使用 const 来声明变量,对不能对其修改,以保护变量。

 

       (2)const 指针

       //不能改变指针指向的值

          const int* ip;

          ip = new int[10];

          ip[4] = 5; // not compile

       

          int const* ip;

          ip = new int[10];

          ip[4] = 5; // not compile

 

          //不能改变指针自身

          int* const ip;

          ip = new int[10]; // not compile

          ip[4] = 5;

 

         //既不能改变指针也不能改变指针指向的值

         const int* const ip = NULL;(无用的指针)

          : const 可以放在类型前也可以放在类型后

  

       (3)const 应用规则

          const 应用于其左则的第一项。

 

       (4)把对象参数传递时,默认的做法是把传递 const 引用。

 

       (5)const 方法

          const 标识类方法,可以防止方法修改类中不可变的数据成员。

 

     3.关键字 static

       (1)关于连接: C++ 中的每个源文件是独立编译的,得到的对象连接在一

          起。

 

       (2)外部连接: 一个源文件中每个名字(如函数或全局变量)对其它源文件

          是可用的。

 

       (3)内部连接: 一个源文件中每个名字(如函数或全局变量)对其它源文件

          可用的。内部连接也叫静态连接。

 

       (4)函数和全局变量默认是外部连接。

 

       (5)声明前加 static 可指定为内部连接。

 

     4.关键字 extern

       (1)作用: static 相对,用来为位于它前面的名字声明外部连接。

       (2)extern 用法

          // AnotherFile.cpp

         extern int x; // 只是声明 x 为外部连接而不分配内存

         int x = 3;    //显示定义以分配内存

       

          extern int x = 3;//声明和定义一起完成

 

          //FirstFile.cpp

         extern int x;

         cout << x << endl;

 

      5.强制类型转换

      (1)const_cast<type> 去除变量的常量性。

      示例:

      void g(char* str)

      {

          // body omitted here

      }

      void f(const char* str)

      {

          g(const_cast<char*>(str));

          // other code omitted here

      }      

 

 (2)static_cast<type> 显示的完成 C++ 语言支持的转换。

    示例:

    int x = 3;

    double result = static_cast<double>(i) /10;

 

    : static_cast 进行类型转换时并不完成运行时类型检查。

 

 (3)dynamic_cast<type>

    1)对类型强制转换完成运行时类型检查。

    2)对指针转换失败时会返回 NULL

    3)对引用转换失败时会抛出 bad_cast 异常。

 

 

6.函数指针

        (1)定义: 把函数地址作为参数,可以像变量一样使用。

        (2)定义函数指针: typedef bool(*YesNoFcn) (int, int);

        (3)用法示例

 

           //定义函数指针类型

           typedef string(*YesNoFcn)(int, int);

 

           void test(int value1, int values2, YesNoFcn isFunction)

          {

             cout << isFunction(value1, value2);

          }

 

         string intEqual(int intItem1, int intItem2)

         {

            return (intItem1 == intItem2) ? "match" : "not match";

         }

 

         //使用函数指针

          test(1, 1, &intEqual);

 

         : & 是可选的

 

 

十六 C++ 中的 I/O 操作

     1.使用流

       (1)每个输入流都有一个相关联的源,每个输出流都有一个相关联的目的。

 

       (2)cout cin 都是在 C++ std 命名空间中预定义的流实例。

 

       (3)流的三种类型:

          1)控制台输入输出流。

          2)文件流。

          3)字符串流。

 

       (4)输出流

          1)输出流在头文件 <ostream> 中定义,输入流在 <istream> 中定义<iosream> 中定义了输入输出流。

 

          2)cout cin 指控制台输入输出流。

 

          3)<< 操作符是使用输出流的最简单的方法。

 

          4)流其它的输出方法

             1)put() wirte()

             2)flush() 刷新输出

 

          5)处理输出错误

             1)cout.good()  流是否处于正常的可用状态。

             2)cout.bad()   流输出是否发生了错误。

             3)cout.fail()  如果最近的操作失败则返回 true

             4)cout.clear() 重置流的错误状态

 

   6)输出控制符

     1)endl         输出回车并刷新其缓冲区

     2)hex oct dec  以十六//十进制输出

     3)setw         设置输出数值数据时的字段占位符

     4)setfill    设置填充空位的占位符

 

       (5)输入流

          1)>> 输入流操作符

          2)输入方法

              1)get()    仅仅返回流中的下一个字符

              2)unget()    引起流回退一个位置

              3)peek()    预览下一个值

              4)getline()    从输入流中取一行数据

 

          3)处理输入错误

             1)good()

             2)eof()

 

       (6)字符串流

          1)<ssteam>    定义了字符串流的头文件

         2)ostringstream  字符串输出流

         3)istringstream  字符串输入流

 

       (7)文件流

          1)<fstream>      定义了文件流的头文件

          2)ifstream    文件输入流

          3)ofstream    文件输出流

          4)seek()    定位流的位置

          5)tell()    查询流当前的位置

 

       (8)链接流

          1)定义: 在任何输入流与输出流之间建立连接,一旦访问就刷新输出。

          2)实现: 用输入流的 tie() 方法

          3)示例

            #include <iostream>

           #inlcude <fstream>

           #include <string>

 

           main()

           {           

                ifstream inFile("input.txt");

               ofstream outFile("output.txt");

    

                //set up a link between inFile and outFile.

                inFile.tie(&outFile);

 

               string nextToken;

               inFile >> nextToken;

         }

 

 

十七 C++ 中的异常

     1.抛出和捕获异常

       (1)<exception> 定义异常类的头文件。

       (2)抛出异常对象 throw exception()

       (3)向量的使用(整型)

        vector<int> myInts;

        for (int i = 0; i < 10; i++)

        {

             myInts.push_back(i);// 增加元素

             cout << myInts[i];

        }

 

       (4)string 风格的字符串转换成 C 风格的字符串

          string myString = "string style string";

          char* cStyleStrng = myString.c_str();

 

       (5)捕获运行时异常

          try

         {

            ...

          }

          catch (const runtime_error& e)

         {

           ...

         }

 

       (6)抛出和捕获无效参数异常

          throw invalid_argument("");

 

         try

          {

            ...

          }

          catch (const invalid_argument& e)

          {

             ...

          }

 

         : runtime_error invalid_argument 定义在头文件<stdexcept>

 

       (7)匹配任何异常(...)

          try

          {

            // code omitted here

          }

          catch (...)

          {

            // code omitted

          }

         : 不建议使用这种方式

 

       (8)使用抛出列表

          1)抛出列表: 一个函数或方法能抛出的异常列表

          2)必须为函数声明和实现都提供抛出列表

          3)没有抛出列表就可以抛出任何异常

         4)空的抛出列表不允许抛出任何异常

 

       (9)在覆盖方法中修改参数列表

          1)从列表中删除异常

          2)增加超类抛出列表中异常的子类

          3)不能完全删除抛出列表

 

       (10)显示异常消息

           可调用 exception 或子类的 what() 方法显示捕获到的异常消息

           示例:

           try

           {

               ...

           }

           catch (const exception& e)

           {

               cerr << e.what() << endl;

               exit(1);

           }

            

       (11)多态地捕获异常

           1)在多态地捕获异常时,要确保按引用捕获异常。如果按值捕获异常,就会遇到切割问题,丢失对象的信息。

 

           2)按引用捕获异常可以避免不必要的复制开销

 

       (12)如果异常处理不够仔细,就会导致内存和资源泄漏

           示例

           func ()

          {

            string str1;

            string* str2 = new string();

            throw exception();

            delete str2;     // 内存泄漏

          }

 

       (13)使用智能指针防止内存泄漏

           #include <memory> // 定义智能指针的头文件

           using namespace std;

           func ()

           {

               string str1;

 

               // 智能指针,不用自己手动释放

              auto_ptr<string> str2(new string("hello"));

              throw exception();

           }

 

       (14)处理内存分配错误

           1)new new [] 分配内存失败默认抛出 bad_alloc 异常

           2)可用 try/catch 捕获异常处理内存分配失败

           3)示例:

             try

             {

                 ptr = new int[numInts];

             }

             catch (bad_alloc& e)

            {

              cerr << "Unable to alloc memory!" << endl;

              return ;

            }

        

          1) new(nothrow) 在内存分配失败时返回 NULL 来处理

          2)示例:

             ptr = new(nothrow) int[numInts];

            if (ptr == NULL)

            {

               cerr << "Unable to alloc memory!" << endl;

               return;

            }

        

           1) set_new_handler() 调函数定制分配失败时的行为

           2)示例

              void myNewHandler()

             {

                cerr << "Unable to allocate memory!" << endl;

                abort(); // 终止程序 <cstdlib>

             }

            

            #include <new>

            #include <cstdlib>

            #include <iostream>

            using namespace std;

     

            int main(int argc, char** argv)

           {

               new_handler oldHandler = set_new_handler(myNewHandler);

 

              // code omitted here

 

             set_new_handler(oldHandler);

             return (0);

         }

 

你可能感兴趣的:(数据结构,C++,exception,String,iostream,spreadsheet)