C++ Primer Plus学习笔记

本文详细介绍了C++中的构造函数和析构函数调用顺序,以及表达式求值的不确定性。深入探讨了引用类型,包括左值引用、右值引用、const引用的使用规则和注意事项。此外,文章还讲解了多态性,包括编译时和运行时多态的实现,以及函数重载和运算符重载的原理。最后,讨论了类的特性,如静态成员、类型转换、友元和继承的相关知识点。
摘要由CSDN通过智能技术生成

C++的表达式求值顺序并没有定义,比如 i=1, r= (i++)+ i*2  ,i++先运算还是i*2先运算是不确定的,取决于编译器,即r的结果可能为5(i++先运算的情况下),也可能为3(i*2先运算)。

 

++i相当于

 

int ppi(int & i)

{

    i = i + 1;

    return i;

}

 

 

 

i++相当于

 

int ipp(int & i)

{   

    int t = i;

    i = i + 1;

    return t;

}

i=1;

 

i++ + ++i的值为 4

 

一、 const及引用(&声明的引用称为左值引用, &&声明的引用称为右值引用)

 

 

    char One = 'A';
    char Two = 'B';
    const char Three = 'C';


    char * const P_One = &One;   //P_One is a const pointer to char, 即是P_One不能指向其它值了;
    P_One = NULL;                //这里会报错,因为P_One是const值;
    const char * P_Two = &Three; //P_Two is a pointer to const char, 即是P_Two是指向const变量的指针,当然P_Two也可以指向非const变量;
    P_Two = NULL;                //这里不会有问题,因为P_Two并不是const值
    char * P_Three = &Three;     //这里会报错,因为非const指针不能指向const变量;

 

 

 

(1)有函数void Test(int &a, int&b);

     调用Test时,传递参数必须为Int型,且不能为表达式。如下:

           int   x = 100,  y =200;  Test(x,  y); //正确

           int   &xx = x ,  &yy = y ; Test(xx, yy); //正确

                                     Test (x + 1,y);//错误

           long  x  =1.0   y = 2.0 ;Test (x,  y); //  错误

 (2)有函数 void Test(const int &a);

       调用Test时,若传递的实参不是int型(即实参与引用参数不匹配), 则自动生成临时变量;

 

            int  x =100;   Test (x + 1); //生成临时变量 ,Test函数中的操作不影响  x  的值, 类似于值传递;

           long  x  =1.0  ;Test (x); //  同上

(3)有函数 int &Test(int & a);返回值为int型引用

        int  x   = 10;         

        int   y = Test(x);// 得到y的值为10, 但y 与x 不指向同一地址;

        int  &y  =  Test(x);//得到的y为x的引用。

       返回引用时不能返回指向Test函数内的临时变量内的引用,因为当Test函数运行完毕,临时变量就会释放;

(4)基类的引用可以直接指向派生类的对象,无需强制转换;

(5)C++ 11中可以使用nullptr表示空指针

 (6)引用只能在定义时进行初始化;

   (7)当函数参数为普通引用时,不能传递一个表达式作为函数参数。因为引用是变量的别名,而表达式并不是一个变量;

             若函数参数是const引用,可以传递一个表达式作为函数参数,此时会创建一个临时变量储存该表达式的值,参数中的引用作为该临时变量的别名;

 

1、多态性 
  指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。 
  a、编译时多态性:通过重载函数实现 
  b、运行时多态性:通过虚函数实现。 

 

二 .函数的重载

   函数特征标:参数个数,参数类型,参数排列顺序;特征标相同则不能重载;

  VS编译器不能区分类型本身和类型引用;

   VS编译器不能区分const和非const变量,但能区分const char * 和char *;

  编译器能区分常量函数(函数声名后加const)与非常量函数;但不区分返回值为常量和非常量。

 (1)使用函数的重载时,需要在函数调用中使用正确的参数类型

          有函数 void print(int,int);

                      void print(long, long);

         调用函数:   unsigned int   x = 1,  y =2;

                                print(x, y);  //  由于实参不与任意函数的形参匹配, C++尝试进行强制转换,但又由于有多个重载函数满足转换,因此这个调用会报错。

   (2)对类的构造函数进行重载后,则不会自动生成默认的构造函数。

   (3)函数模板也可以进行重载;

 

三.类

   1、同一个类的不同对象有各自的变量储存空间,但共享同一块代码块。

   2、在一下的Test类中const_test()函数之后又const修饰,则该函数中不能修改类中的成员函数a的值;

class Test
{
  private:
         int a;
  public:
        void const_test() const;
}

3、被声明为const的成员函数(函数声明后加const)才能被一个const类对象调用

 

const对象只能访问const成员函数。因为const对象表示其不可改变,而非const成员函数可能在内部改变了对象,所以不能调用。

而非const对象既能访问const成员函数,也能访问非const成员函数,因为非const对象表示其可以改变。

4、

5、

6、静态类成员,如static int num ;,无论创建了多少对象,程序只创建一个静态类成员副本,即所有类对象共享同一个静态类成员。

     静态类成员是整型或者枚举型cons型时,可以在类声明中初始化。其他的可以在类声明之外用独立语句进行初始化,类似于类中的方法。

7、使用new分配的内存,使用delete释放;使用new [ ]分配的内存,使用delete[ ]释放。

8、当使用一个类对象去初始化另一个类对象,程序将自动生成并使用“复制构造函数”。(P432)  按值传递类对象或者返回类对象都会调用复制构造函数,

 

T  x;
T  y =  x;//此处相当于 T y = T(x),即自动生成的T的构造函数的原型为T(const &T)以生成一个临时对象,然后通过自动生成的重载赋值运算符(=)把临时变量赋给y; 

复制构造函数完成的功能是将x中成员的值赋给y中的成员,比如类T中有成员 int  A ;   char  *B ,则x,y的A,B值相同。需注意,由于B是指针,则x,,y中的B指向同一个字符串,如果对x进行操作时释放了x.B指向的堆空间,则不能再释放y.B指向的堆空间,否则重复释放已释放的地址。

而自动生成的赋值运算符的赋值方式与默认的复制构造函数相同,也是简单地将值赋给对象成员。

解决上述问题的方法是:显示地定义复制构造函数,以免编译器自动生成。

9、静态类成员函数:不能通过类对象调用,但可以通过类名和作用域解析运算符进行调用。并且静态类成员函数只能操作静态数据成员;

 

class  String
{
   static int num;
   static int how_many()
      {
          return num;
      }
}

int a =  String::how_many();

 

 

10.类成员中的const成员数据和被声明为引用类型的成员都只能通过  初始化列表进行初始化。

11、类的protected成员:在类外只能通过公有成员函数访问(与private成员一样),但派生类可以直接操作基类的protected成员,而不能直接操作基类的private成员;

12、const 成员函数:在类成员函数的括号后加const。const成员函数不能修改类成员对象。

                                       只要类方法不修改类成员对象,则应定应为const。

class T
{
    public:
           void show() const;
}

void T::show() const
{
   return;
}

 

 

13、静态类成员变量:带static的类成员变量。静态类成员变量与其它静态变量存储在一起,而不是存储在变量中,因此该类的所有对象共享一个静态类成员变量;

   
 

 

四、运算符重载

1、重载的运算符必须至少有一个用户自定义的类型。

2、使用运算符时不能违反运算符原来的语法规则。

3、大多数运算符都可以通过成员或非成员函数进行重载,但=、()、[ ] 、->只能通过成员函数进行重载。

4、在类的成员函数中重载了运算符,调用该运算符时必须将类对象在运算符左侧。

     class  Test

     {

       public:

               int    a;

               int    b;

              void   operator*(int t)

               {a = a*t;  b = b* t; }

      }

调用时必须为    Test T1,;  

                           T1 *2;   //此处必须是T1在运算符左侧,不能是2*T1。

5、非成员函数重载运算符时,调用时在运算符左侧的值对应函数第一个参数,右侧的值对应第二个参数

 

              void   operator*(Test tt, int t)

               {tt.a = tt.a*t;  tt.b = tt.b* t; }

               调用时必须为tt*2;    

常用的非成员函数重载运算符是<< :

              ostream &operater <<(ostream os,  Test   t)    //要在Test类中声明此重载函数为友元函数

             {

                        os << t.a << endl;

                        return  os;

             }

       

             即可调用  cout <<  t  << "it is t";进行Test类成员的输出。

 

6、在类中重载了运算符,但调用时可能要将类对象放在运算符右侧,此时可以使用非成员函数重载运算符,并将类对象作为第二个函数参数,最后将此函数作为友元函数;

7、重载的限制:

   1)重载后的运算符至少要有一个操作数是用户自定义的类型;

  2)使用重载后的运算符不能违反原来运算符的句法规则;

  3)不能创建新运算符;

  4)部分运算符不能重载:sizeof(),.,::等;

  5)部分运算符只能通过成员函数进行重载:=,(),[],->;

 

8、类型转换操作符(type conversion operator)是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。

   

T1::operator T2() const;   //T1的成员函数,"(T2)a"类型转换

 

五、友元

友元函数具有与成员函数相同的对类成员数据的访问权限;

1、非成员函数重载运算符,参数中含有类对象,并且在重载函数中需要调用类中的私有参数时,可以在类中将该重载函数声明为友元函数;

     class  Test

     {

       public:

               int    a;

              friend   void   operator*(int x ,Test   t);

      }

六、类型转换

1、在函数前加上关键字explicit,则该函数的参数不支持隐式转换,只支持显示转换。

 

class Test
{
     public:
             double  a;
             explicit Test(double   x)
             {
                  a = x;
             }
}

Test   tt = 10; //隐式转换,invalid
Test   tt(10);//显示转换,valid

2、在类中定义转换函数可实现将类对象转化为特定类型数据

 

转换函数原型:operator  typename( );

Test::operator  int( )
 {
     return int(a);
}

将Test对象转为int型,调用方式为 : 

 

Test  tt(10);;   

int   i = tt;

 

七、类的继承

      1、派生类的构造函数必须以初始化列表的形式显式地调用基类的构造函数,除非使用基类的默认构造函数,

      2、派生类对象被删除时先调用派生类的析构函数,再调用基类的析构函数。

      3、基类指针可以不经显式转换即可指向派生类对象,基类引用可以不经显式转换即可引用派生类对象,但只能调用基类方法;

            反向则不成立。

      3.1、重新定义继承的方法并不是重载,如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明, 而是隐藏同名的基类方法。

     3.2、基类中有方法重载时,则在派生类中重新定义所有的基类版本,否则未定义的版本将被隐藏。

      4、虚方法:使用virtual关键字声明的方法,一般是派生类中重新定义基类中的方法时使用;

         1)方法在基类中声明为虚方法,则在派生类中自动成为虚方法;   

         2)声明为虚方法 后, 程序将根据引用或指针指向的对象的类型选择方法,否则将根据引用或指针的类型来选择方法。

      5、派生类可以在成员函数内部直接访问基类的public成员和protected成员,但只能通过基类的public方法访问基类的private成员;若通过对象的形式则只能访问基类的public成员;

      6、抽象基类(虚基类):类声明中含有纯虚函数。不能实例化虚基类,只用作基类,并在派生类中实现基类中的纯虚函数。

           纯虚函数:在类中函数声明结尾处为“ = 0”的函数。(即使提供了对该函数的定义,也不影响其为纯虚函数)P510。

      7、基类中使用了动态内存分配,而派生类中没有使用时,只需要在基类中显示定义赋值函数和复制构造函数、析构函数,在派生类中可以使用默认生成的赋值函数和复制构造函数。

            基类中使用了动态内存分配,而派生类中也有使用时,需要在基类中显示定义赋值函数和复制构造函数、析构函数,在派生类中也要定义赋值函数和赋值构造函数、析构函数。构造函数中需以初始化列表的形式调用基类的赋值构造函数,赋值函数需通过作用域解析符显示调用基类的赋值函数,析构函数则自动调用。

            P516

     8、使用using重新定义访问权限       

 

class father
{
protected:
	void father_print()
	{
		cout << "print method in father" << endl;
	}
};


class child : private father
{
public:
	using father::father_print;    //此时father_print成为child的public接口;若无此using语句,father_print只为child的私有成员;
};

 

 

 

 

 

1)、公有继承(public):基类的public成员成为派生类的一部分;派生类可以通过基类的public、protected方法访问基类的私有成员。

2)、私有继承(private):基类的public成员,protected成员都成为派生类的私有成员;派生类仍只能通过基类提供的public,protected方法访问基类私有成员;

3)、保护继承(protected):基类的public成员,protected成员都成为派生类的protected成员;派生类可以通过基类的public、protected方法访问基类的私有成员。

 

 

1、构造函数的调用顺序 

 

基类构造函数、对象成员构造函数、派生类本身的构造函数  

2、析构函数的调用顺序

派生类本身的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好相反) 

 

 

 

   9、虚基类

  1)派生类在继承基类时使用关键字virtual,如 class  B:public virtual A {},则A为虚基类;

  2)虚基类与虚函数并没有明显的联系,只是都使用关键字virtual;

  3)虚基类的作用:派生类同时继承多个类,而这多个类的基类相同并且继承的是虚基类,则派生类只会继承一个基类对象;

                                   B1,B2以virtual方式继承A, C继承B1,B2,则C只继承了一个A对象。若B1,B2不以virtual方式继承,则C有两个A对象;

  4)基类是虚的时,禁止信息通过中间类自动传递给基类。如C中信息不能通过B1,B2传递给A,只能显式地调用A的对象;

       因此在构造函数中,若不希望调用基类的默认构造函数,则应显式地调用基类的构造函数。

  5)当类通过多条虚途径和非虚途径继承某个特定的基类,该类中将包含一个表示所有虚途径的基类子对象和多个分别表示非虚途径的基类子对象。

 

10、类模板

1)

template <class Type>
class Test
{
    Test();
    Test&  tt(Type a);   //此处Test是Test<Type>的缩写,只能在类声明中使用,在类外必须使用Test<Type>
}

Test<Type>::Test()
{}

Test<Type>&  Test<Type>::tt(Type a)
{}

 

(2)类模板中可以使用多个类型参数,并且可以指定默认值

template <class Type1, class  Type2 =  int>

class Test

{}

 

(3) 显示具体化 ,部分具体化

template<>

class Test(int, int)   //显示具体化,当Type1,Type2的值与此定义相同时,将调用此类定义,而不是类模板

{}

 

//部分具体化

template<Type>

class Test(Type ,int) //当Type2的值与此定义相同时,调用此类定义而非类模板

 

当有多个类定义符合时,编译器会选择具体化程度最高的类定义

      

(4)类模板中的成员模板函数

template<class  T>

class  Test

{

     template <class  U>

    void in_test(T a,   U b);

}

 

template <typename T>   //由于模板是嵌套的,所以必须这样

   templste  <typename  U>

void Test<T>::in_test(T a,   U b)

{

}

 

(5)将模板用作参数

     template <template  <typename  T> class  Thing>   //Thing为一个模板类

    class Test

   {

      Thing<T>  s1;

   }

 

(6)声明和定义一个模板的时候,必须要让声明和定义放在一个文件里。否则编译器会报错.

(7)模板类的约束模板友元函数:友元函数的模板类型受模板函数的模板类型约束

 

template<class T>
void friend1()
{
 

}


template<class T>
void friend2(T& p)
{
std::cout << p.one << p.two << std::endl;

 

}

 

template <class T1, class T2>
class pair
{
private:
T1 one;
T2 two;
public:

        friend void friend1<T1>();
friend void friend2< pair<T1, T2 > >(pair<T1, T2> &p);         //其中的< pair<T1, T2> >表明friend2是模板,<>中间值可为空,因为参数(pair<T1, T2> &p)可表明friend2的模                                                                                                                      板参数类型
};

 

八、指针

1、智能指针auto_ptr:当指针变量实效后能自动释放该指针指向的内存;

 

#include <memory>
using namespace std;
int main()
{
    auto_ptr<string> ps(new  string);
    return 0;
}


C++98只支持auto_ptr,C++11支持unique_ptr ,shared_ptr;

 

注意auto_ptr不适用的情况:

      若有auto_ptr<string> ps2 = ps;则不能通过ps访问原来的string,因为赋值之后auto_ptr将把所有权转让给ps2。这样的目的是避免多个智能指针指向同一地址,造成同一地址的重复释放;

 

九、内存管理

1、变量

(1)自动变量:函数中声明的变量。存储在栈中,由程序自动管理。仅在定义该变量的代码块中有效;

(2)静态变量:在函数外定义的变量或用static声明的变量,在整个运行过程中都存在,放在特定的内存中。默认为0.

          静态变量有三种:

         a、在函数外定义的,不带static声明的变量:具有外部链接性,可在其他文件访问;其它文件要使用此变量时,先要使用extern进行引用声明;

               //file1.cpp

              int   status = 0;   //此为定义声明,extern int status = 0;效果相同,因为都有初始化;

             //file2.cpp

            extern int status; //file2中要使用file1的全局变量时必须用extern进行引用声明,且不能初始化;

             

         b、在函数外定义,带static声明的变量:内部链接性,只能在本文件访问;使用const修饰的全局变量也具有内部链接性;

               可以将具有内部链接性的静态变量放在同一个头文件中,各个源文件包含此头文件,即可使得各源文件具有自己的一组变量,而对于外部链接性的变量则会是重复定义;

              使用extern关键字即可以有外部链接性,extern static int   T =1;

         c、在函数内定义,带static声明的变量:不具备链接性,只能在当前函数或代码块中使用;

注:局部变量会隐藏掉全局变量;

       在使用变量时在变量名前加::,表示使用该变量的全局版本;

(3)多个源文件cpp中使用同一个变量:

        在一个头文件h中用extern声明变量,不赋值,源文件包含该头文件,在某个源文件中定义该变量,赋值,则其他源文件可以直接使用该变量

 

 

  • 在构造函数中抛出异常是C++中通知对象构造失败的唯一方法 就是try{}catch{}, 在catch中进行内存的释放。

  • 构造函数中抛出异常,对象的析构函数将不会被执行。

  • 构造函数抛出异常时,本应该在析构函数中被delete的对象没有被delete,会导致内存泄露。

  • 当对象发生部分构造时,已经构造完毕的子对象(非动态分配)将会逆序地被析构。

 

 

 

 

 

十、容器对比

C++中的容器类包括“顺序存储结构”和“关联存储结构”,前者包括vector,list,deque等;后者包括set,map,multiset,multimap等。若需要存储的元素数在编译器间就可以确定,可以使用数组来存储,否则,就需要用到容器类。

 

1、vector:向量是表示容量动态变化的数组的序列容器。就像数组一样,向量对它们的元素使用连续的存储位置,这意味着它们的元素也可以使用其元素的正则指针的偏移量来访问,并且和数组一样高效;

                    为了适应元素数量可能的增长,vector的实际容量是比所需容量要大的;但在元素数量发生指数级的增长时,vector会自动申请一个新的数组并把全部元素移动过去,这样做的开销是很大的。

                    相比于其他的动态序列容器(deques, lists and forward_lists),vector在元素的随机访问方面是非常高效的,在尾端的插入和删除也相对高效。但在非尾端的元素插入和删除,vector的表现比其它容器差。

 

2、deque:双端队列double-ended-queue。deque提供类似vector的功能,deque的随机访问效率接近于vector,但在首端和尾端的插入和删除都很高效。deque的元素可以分布在不同的内存块中,不可以通过指针偏移量来访问元素(但可以通过[]来访问,比vector稍慢),但这样更适应与元素的快速增长,因为不会发生元素的全部迁移。相比于lists and forward_lists,deque在非首尾段的插入和删除的效率较差;

 

3、list:列表是序列容器,允许在序列内的任何地方进行恒定时间插入和擦除操作,并且在两个方向上进行迭代。列表容器实现为双向链表;双链表可以存储它们包含在不同和不相关的存储位置中的每个元素。与数组、vector、deque对比,list的在任意位置的插入和删除,移动会很高效,但list和forward_list不能实现元素的随机访问,并且会消耗额外的存储器来保持元素之间的关联信息。

 

4、forward_list:单向链表。

 

5、set:set是按特定顺序存储唯一元素的容器。set中的元素值即是元素的键,set中元素是唯一的且不能被修改,否则其顺序将被破坏,但可以删除插入。通常实现为二叉搜索树。

 

6、map:map存储由键值和映射值的组合形成的元素,遵循特定顺序;通常实现为二叉搜索树。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值