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

  • 相关阅读:
    现在分词和过去分词
    VMware Workstation Ubuntu 20.04 LTS无法连接网络问题
    Java中定时器Timer致命缺点(附学习方法)
    2020 年度编程语言排行榜出炉!C 语言称霸,Java 遭遇滑铁卢…….
    人工智能必备数学基础:线性代数基础(1)
    初学VBA
    何同学新视频火了!找到减少沉迷手机的最佳方法:附免费APP
    支付宝蚂蚁森林下线能量提醒功能 产品经理:被骂了、我改
    可抵御所有已知黑客攻击 中国组建天地一体化量子通信网络
    MYSQL数据库 增删改查基础语句
  • 原文地址:https://www.cnblogs.com/cloudseawang/p/1142299.html
Copyright © 2011-2022 走看看