zoukankan      html  css  js  c++  java
  • 好久没有用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)对简单内置类型(如int或double)要传值,其它所有情况可传引用。

           (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);
             }

  • 相关阅读:
    [MacOS]Sublime text3 安装(一)
    [RHEL8]开启BBR
    PAT Advanced 1136 A Delayed Palindrome (20分)
    PAT Advanced 1144 The Missing Number (20分)
    PAT Advanced 1041 Be Unique (20分)
    PAT Advanced 1025 PAT Ranking (25分)
    PAT Advanced 1022 Digital Library (30分)
    PAT Advanced 1019 General Palindromic Number (20分)
    PAT Advanced 1011 World Cup Betting (20分)
    PAT Advanced 1102 Invert a Binary Tree (25分)
  • 原文地址:https://www.cnblogs.com/cloudseawang/p/1142299.html
Copyright © 2011-2022 走看看