C++知识总结

来源:互联网 发布:软件实施部门管理制度 编辑:程序博客网 时间:2024/05/12 09:51

 1、类型转化


         显式类型转换被称为“强制类型转换”(cast)
         标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。
      1)static_cast:


         用法:static_cast< type-id > ( expression )
         说明:该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
         需要static_cast强制转换的情况:
         情况1:void指针->其他类型指针
         情况2:改变通常的标准转换
         情况3:避免出现可能多种转换的歧义
         它主要有如下几种用法:
         用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
         用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
         把void指针转换成目标类型的指针(不安全!!)
         把任何类型的表达式转换成void类型。
         注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
         static_cast把子类的指针或引用转换成基类
      2)dynamic_cast


         用法:dynamic_cast< type-id > ( expression )
         说明:该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
         解释:基类中如果没有f()函数或者是没将该函数设置为virtual函数时,此时又在子类中有该方法,利用
         dynamic_cast可以将子类的一个对象传给某个函数(参数为基类),此时这个基类就可以访问这个f()函数
         class基类{
         //或者没有virtualf();
         };
         class子类{
         f();
         }
         voidpayroll(基类 *pe){
                   子类 *pm =dynamic_cast<子类 *>(pe);
                   //如果pe实际指向一个Programmer对象,dynamic_cast成功,并且开始指向Programmer对象起始处
                   pm->f();//此时这里可以
         }
         payroll(子类);//但这个时候的实参必须为子类
         dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。dynamic_cast还支持交叉转换(cross cast)。如
         子类2 *pd2 =static_cast<子类2 *>(子类1对象); //compile error
         子类2 *pd2 =dynamic_cast<子类2 *>(子类1对象); //pd2 is NULL
        
      2、文件读写和字符串流


        
ifstream in("address.txt");
   for(string s; getline(in,s);)
{
    //以字符串数组作为输入源
       istrstream in1(s.c_str(), 0);
       pstu p=new stu;
       in1>>(*p).m_no>>(*p).m_name>>(*p).m_sex>>(*p).m_age>>(*p).m_tel>>(*p).m_address>>(*p).m_qq;
       p->m_next =NULL;
       (*head).m_next=p;
       head=p;
    }
 
字符串流
他们创建对象就必须包含strstream.h头文件。
istrstream类用于执行C风格的串流的输入操作,也就是以字符串数组作为输入设备。
istrstreamin1(s.c_str(), 0);或者是istrstream in1(s.c_str(), s.length+1);
参数1表示字符串数组,而参数2表示数组大小,当size为0时,表示istrstream类对象直接连接到由str所指向的内存空间并以/0结尾的字符串。
ostrstream类用于执行C风格的串流的输出操作,也就是一字符串数组作为输出设备。
ostrstream::ostrstream(char *_Ptr,int streamsize,int Mode= ios::out);
  第一个参数是字符数组输出到这个字符串里面去,第二个是说明数组的大小,第三个参数是指打开方式。
               如char *pbuffer=newchar[arraysize]; 
         ostrstream ostr(pbuffer,arraysize,ios::out);
          ostr<<arraysize<<ends;
strstream类同时可以支持C风格的串流的输入输出操作。
 
      3、名空间


         往名空间nameH里加东西时,我们只需要再次定义相同的名空间名。
         namespacenameH
         {
                   new
         }
      4、变量名取名


         变量名由作用域前缀+类型前缀+一个或多个单词组成。为便于界定,每个单词的首字母要大写。
         作用域前缀              说明
     无               局部变量
         m_                类的成员变量(member)
         sm_                 类的静态成员变量(staticmember)
         s_              静态变量(static)
         g_              外部全局变量(global)
         sg_              静态全局变量(static global)
         gg_              进程间共享的共享数据段全局变量(globalglobal)
        
         类型前缀标明一个变量的类型,可以有如下几种:
         类型前缀前缀              说明
         n             整型和位域变量(number)
         e            枚举型变量(enumeration)
         c             字符型变量(char)
         b             布尔型变量(bool)
         f             浮点型变量(float)
         p             指针型变量和迭代子(pointer)
         pfn             特别针对指向函数的指针变量和函数对象指针(pointerof function)
         g             数组(grid)
         i             类的实例(instance)对于经常用到的类,也可以定义一些专门的前缀,如:std::string和std::                         wstring类的前缀可以定义为"st",std::vector类的前缀可以定义为"v"等等。
         如:m_nState
      5、宏定义中的特殊参数(#、##、...和__VA_ARGS__)的用法(注意,这些只能用在宏定义中,不能用在其他地方)


      1)__VA_ARGS__以及...


         __VA_ARGS__用来代替...,比如说在一个宏中由于不知道有多少个参数,可以直接用...来表示,通过__VA_ARGS__来读取...中的内容如:#define check(...) printf(__VA_ARGS__)
         如果我在下面调用
         check("a= %d,b = %d", a, b);此时...代表了"a = %d,b = %d", a, b,而通过__VA_ARGS__来读取...的内容,即也为"a= %d,b = %d", a, b
         即printf(__VA_ARGS__)变为printf("a= %d,b = %d", a, b)
         这样,在一些情况下要定义自己的打印函数就特别方便了,比如我们要在A条件成立,且B条件不成立的情况下打印输出
         #definecheck(...) printf(__VA_ARGS__)
         #definecheck1(a,b,...) if (a&&!b){ printf(__VA_ARGS__);}else{printf("a =%d \n",a);}
         inta = 1;
         intb = 0;
         check("a= %d,b = %d \n", a, b);
         check1(a,b, "a = %d,b = %d \n", a, b);
         结果为:a = 1,b =0
                            a= 1,b = 0
      2)#


         #作为一个预处理运算符,它可以把语言符号字符串化.例如我们定义的变量等,如果是在字符串中使用时(但是其前后必须是字符串形式),将其语言符号字符串化后并将其与左右连接起来,
         如:
         #defineTEST(x) printf("square of  " #x" is %d.\n",(x)*(x)) 
         intc = 10;
         TEST(2);
         TEST(c);
         TEST(c- 2);
         结果:
         squareof  2 is 4.
         squareof  c is 100.
         squareof  c - 2 is 64.
         特别重要的是,#和__VA_ARGS__以及...的搭配使用,可以将本不会将某些东西变为字符串形式变为字符串形式,如:
         #definetoString(...) #__VA_ARGS__
         char*shader = toString(
                   uniformsampler2D inputImageTexture;
                   uniformvec2 samplerSteps;
                   voidmain()
                   {
                            printf("ajlksajala!\n");
                   }
                   );
         check(shader);
         此时将uniformsampler2D inputImageTexture;
                   uniformvec2 samplerSteps;
                   voidmain()
                   {
                            printf("ajlksajala!\n");
                   }
                   变成了字符串的形式
         结果为:
         uniformsampler2D inputImageTexture;uniform vec2 samplerSteps;voidmain(){printf("ajlksajala!\n");}
      3)##


         ##运算符可以用于类函数宏的替换部分.##还可以用于类对象宏的替换部分.这个运算符可以把两个语言符号组合成单个语言符号.
    例如:
         //在定义一个变量时
         #definexName(n) var##n
         //定义变量var2
         intxName(2,"var");
         var2= 3;
         check("var2= %d \n",var2);
         结果:
         var2= 3
 
6、操纵算子


1)自带的操作算子


C++输入输出流的操纵算子(manipulator)有:endl、flush、ws、hex等。
iostream.h还包括以下的操纵算子:
int number = 10;
    cout<<number<<"  ";
    cout<<flush;   // 清空流  
    cout <<"16进制为:"<< hex << "0x" << number;  // 输出16进制  
    //cin>>ws; // 跳过空格
 
for(int j = 0;j < 20;j++){
       //右对齐,宽度为3
       cout<<right<<setw(3)<<j<<endl;
    }
 
2)自定义操纵算子
我们可能想建立自己的操纵算子,这是相当简单的。一个像endl这样的不带参数的操纵算子只是一个函数,这个函数把一个ostream引用作为它的参数。对endl的声明是:


ostream&endl(ostream&);
例子:产生一个换行而不刷新这个流。人们认为nl比使用endl要好,因为后者总是清空输出流,这可能引起执行故障。
 ostream& nl(ostream& os) {
   return os << "\n";
 }
 int main() {
   cout << "newlines" << nl << "between" << nl << "each" << nl << "word" << nl;
  return 0;
 }
7、类里的const和enum


下面的写法有什么问题吗?:
1.  class bob {
2.      const size = 100;  // illegal
3.      int array[size];   // illegal
 }
结果当然是编译不通过。why?因为const在类对象里进行了存储空间分配,编译器不能知道const的内容是什么,所以不能把它用作编译期间的常量。这意味着对于类里的常数表达式来说,const就像它在C中一样没有作用。
在类里的const意思是“在这个特定对象的寿命期内,而不是对于整个类来说,这个值是不变的”。那么怎样建立一个可以用在常数表达式里的类常量呢?
一个普通的办法是使用一个不带实例的无标记的enum。枚举的所有值必须在编译时建立,它对类来说是局部的,但常数表达式能得到它的值,这样,我们一般会看到:
 class bob {
     enum { size = 100};  // legal
     int array[size];      // legal
 }
使用enum是不会占用对象中的存储空间的,枚举常量在编译时被全部求值。我们也可以明确地建立枚举常量的值:enum { one=1,two=2,three};
 
8、外部变量extern


用extern声明外部变量
   (1)在同一个文件内,若定义在文件中间,要在文件前面使用,可以在前面用extern声明
   (2)在相关联的不同的文件内,若文件A定义了变量a(当然这个变量是全局变量),文件B要使用变量a(此时文件B已经将A文件包含进来了),若再定义变量a会出现重复定义错误,此时在文件B中只要加上extern a(不用加类型如int,但建议加上,否则可能出现不可预期的错误,实际操作中的经验),就可以使用(如果不想要某变量被另一个文件外部引用,可以加上static以防止外部引用)
 
9、typedef


 我们可以讲Typedef当做是为某种数据类型取别名,这样我们就可以直接用这个别名来取代之前的数据类型来申明变量
Typedef数据类型 别名
typedefint INTEGER 这样在程序中可以用INTEGER代替int来进行定义,如INTERGER a;
  typedef struct
   {
int month;
int day;
int year;
  }DATE;             
这样可以用DATE定义变量:DATEbirthday;  DATE *p;它代表一个结构体类型


   typedef int ARR[10];   这样用ARR a,b,c就等同于inta[10],b[10],c[10];
  
   typedef 与#define的区别:(如typedef intCOUNT与#define COUNT int)
#define 在预编译时处理,它只能做简单的字符串替换,而typedef是编译时处理的,并非简单替换。typedef有利于程序的通用和移植
 typedef int (*pcb)(int , int) ;
  pcb p1,p2;
 以上等价于: int(*p1)(int,int);
                       int (*p2)(int,int);
 
10、模板


类模板
template是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数,也可以是非类型参数。类型参数由关键字class或typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量
//最简单的  template<class T>
//默认设置通用数据类型  template<class T =double>
//非类型参数  template <classtype=double, int size >
template<class T>
class templateClass{
public:
   templateClass(T data){
      this->data = data;
   }
 
   T data;
   void print(){
      cout<<data<<endl;
   }
 
   static void test();
};
模板类再调用时,要对通用数据类型初始化设置,像上面的我们可以这样初始化为: templateClass<int>
模板函数
为某个函数中设置某个量为通用的数据类型,这样就可以时不同的数据类型也可以作为这个函数的参数,调用时直接调用,不需要对这个通用数据类型初始化
template <class T>
void templateFun(T data){
  cout<<data<<endl;
}
templateFun(10);
 
11、运算符重载 


格式:
返回的数据类型  operator需要重载的符号(数据类型 &c2);
如复数的相加,在类中声明一个函数 Complex operator+(Complex &c2); 
 ComplexComplex::operator+(Comlex &c2)   {
     return Complex(real+c2.real,imag+c2.imag);
    }
   调用
        Complexc1(1,2),c2(2,3),c3;
        c3 = c1+ c2;   //即可完成相加的结果
 但实现重载 = 操作符的时候应该注意自我赋值 问题,如
   class Bitmap{...};
    class Widget{
            ....
            private:
               Bitmap *pb;
    };
   在这里有两种方式:
    a. 证同测试 (identitytest)
  Widget& Widget::operator=(const Widget& rhs)
    {
       if(this == &rhs) return *this; 
        delete pb;
        pb = new Bitmap(*rhs.pb);  //在这里可能会出现异常
        return *this;
    }
    具备自我赋值安全性,但是不具备异常安全性(因为无论什么情况导致newBitmap出现错误,pb的指向都会出错)。
   b. 异常安全性测试
  Widget& Widget::operator=(const Widget& rhs)
    {
        Bitmap *pOrig = pb; 
        pb = new Bitmap(*rhs.pb);   //在这里可能会出现异常
        delete pOrig;
        return *this;
    }
   通常保证了异常安全性的都能够保证自我赋值安全性。
    这里即便new操作抛出异常,pb还是指向原位置, 他并不高效,但是行得通。
 
12、 explicit(显式)


c++中的explicit关键字用来修饰类的构造函数,表明该构造函数是显式的。所谓构造函数是显示的还是隐式的说明如下
a构造函数隐式


 class Myclass  {
        public:
        Myclass(int a);
//这种为隐式
    };
   Myclass m = 10;   此时相当于 Myclass temp(10);     Myclass m = temp;
b、构造函数显式


class Myclass  {
        public:
        explicit Myclass(int a);
//这种为显示
   };
这种情况下Myclass m =10 不会通过自动转换,会出现错误,
此时就必须
Myclass m(10);

0 0
原创粉丝点击