zoukankan      html  css  js  c++  java
  • 3、C++快速入门

    参考书籍:

    C++程序设计教程_第二版_钱能    //篇幅较少,适合快速学习

    C++ Primer Plus  第六版  中文版   //篇幅较大,讲的非常详细

    C++一般必须包含的头文件是#include <iostream>;导入的命名空间是:using namespace std;

    1、访问控制,类和对象

    class是对struct的扩展,含有数据成员和成员函数

    访问控制:private、public、protected,其中private声明的数据成员仅供类内部函数使用,public声明的类外部程序也可使用,默认的属性是private

    int a:int是类型,a是变量

    Person per :Person是类,per是对象

    (在类中通过”this->数据成员”表示类内成员)

    2、程序结构

      类定义(.h)/类实现(.cpp)

      如果在类中仅声明了函数,在类外怎么实现?void 类名::函数名(参数){函数体}

      A类实现Person类,其提供.h和.cpp,在A.h中仅声明类,其中类成员函数也仅声明,在A.cpp中通过类名::函数名来实现成员函数;B类实现main,B不关心Person类怎么实现,只需要#include <person.h>

      

      命名空间

      如果main文件中include多个h文件,同时这些h文件中存在同名的函数,其返回值和参数都一样,这个时候在h和其对于的cpp中把全部或者部分同名的函数使用namespace 名字A{}扩起来,在main中通过“名字A::同名函数名”还区分

    eg:Person.h

     #include <stdio.h>

      namespace A{

      class Person{

      private:

        char *name;

        int age;

        char *work;

      public:

        void setName(char *name);

        int setAge(int age);

      }

      void printVersion(void);

      }

    Person.cpp

      #include <stdio.h>

      #include "person.h"

      namespace A{

      void Person::setName(char *name)

      {

        this->name = name;

      }

      int Person::setAge(int age)

      {

        this->age = age;

        return 0;

      }

      void printVersion(void)

      {

        printf("Person111111");

      }

      }

    Main.cpp

      #include <stdio.h>

      #incldue "person.h"

      using A::Person;//加上这句后,main中类的声明前面可以不加“命名空间::”,这句话把A::Person放入global namespace,以后可以使用Person来表示A::Person

      //using namespace A;作用同是把A空间全部导入,注意在导入多个命名空间的时候,如果命名空间中存在同名同参函数,在使用的时候还是要加上“命名空间::”来区分,但在导入的时候不会出现问题

      int main(int argc,char **argv)

      {

        A::Person per;//声明命名空间后,类的声明需要在前面加上“命名空间::”

        A::printVersion();

        return 0;

      }

    3、重载、指针和引用

     重载:函数名系统,参数不同(类型、数量、顺序)

     指针和引用:引用就是别名,引用定义时必须初始化,且其只能引用变量,不能是常量:int a;int &b = a//b就是a的引用,b所指的内存和a一样;C++中经常使用引用来传递参数,引用传递的是地址,仅四字节,否则如果参数是对象,则传递对象耗费空间大

     引用举例:

     int add(int &b)

      {

        b = b +1;

        return b;

      }

      调用add:

      int a = 99;

      add(a);

      cout<<a<<endl;//引用会导致a变量被修改

    4、构造函数   

      声明变量时调用构造函数

      eg:Person per(变量1,变量2);//会调用构造函数void  Person(变量1,变量2){};

      注意:调用默认构造函数是通过Person per,而不是Person per();其会被理解为一个函数声明,返回值是Person

      Person *per4 = new  Person;

      Person *per5 = new  Person();

      Person *per6 = new  Person[2];

      Person *per7 = new  Person("list",18,"student");

      Person *per8 = new  Person("list",18);

      如果在构造函数中通过new分配了空间,应该在析构函数中delete掉,否则只能等到主函数推出后才能被回收,析构函数在实例化对象被销毁之前的瞬间被调用,比如在一个函数中会声明这个实例化对象,在函数执行完退出时销毁该实例化对象。但是如果在函数中是通过new来实例化一个对象的时候(Person *per4 = new  Person;),函数退出时per4 指针所指向的对象不会被销毁,其析构函数不会被调用,只能通过delete per4来销毁或者等整个main程序退出的时候才能回收空间,但次数不会调用对象的析构函数。

      eg:PersonPerson(){

          this->name = new char[10];

        }

        ~PersonPerson(){

          if(this->name)

            delete this->name;

        }

      对象有默认的无参构造函数、无参析构函数、还有一个默认的拷贝构造函数,即在声明一个实例化对象的时候提供的参数是对象:Person per("zhangsan",18);Person per2(per);即per2使用per来初始化的,这个时候调用的构造函数就是默认的拷贝构造函数,per2的属性内容和per一样,共享地址空间,如果per内存被释放,per2在执行释放的时候也会再次释放该内存,存在风险,所以需要提供自己的拷贝构造函数:

    eg:Person(Person &per)

      this->age = per.age;

      this->name = new char[strlen(per.name)+1];

      strcpy(this->name,per.name);

      函数中定义的静态实例化对象在函数退出的时候不会被销毁;再次进入函数的时候也不会被创建,其还存在

      全局对象、main中局部对象、子函数中局部对象中的构造函数执行顺序:1、全局对象构造函数->main中局部对象构造函数->子函数中局部对象构造函数

      如果在类A中声明了成员类对象B,则在实例化A对象的时候先调用B的构造函数,在调用A的构造函数;析构函数是先调用A的析构,在调用B的析构,即析构函数的调用顺序与构造函数的调用顺序相反(不管A中有多少个对象)

      如果类中定义了有参构造函数,则系统不会在提供无参的构造函数,这时候在声明无参对象时会出错,因为已经没有无参构造函数了

    5、静态成员和友员函数

      static修饰的成员属于类,不属于对象,其仅有一份,通过“类名::成员名”访问,并且私有的static成员仅能被static函数访问

      并且static成员必须在类外面定义和初始化:int Person::cnt =0;在类中仅是声明,在类外给其分配空间和初始化

      被类设置为的友员函数可以访问本类的私有成员:eg:在类中通过“friend 函数声明”来声明该类的友员函数

    6、操作符重载-通过类外函数实现

      通过操作符重载可以实现通过“+”、“-”等符号实现对象的加减

      class Point{

      private:

          int x;

          int y;

      friend Point operator+(Point &p1,Point &p2);

      }

      Point operator+(Point &p1,Point &p2)

      {

        Point n;

        n.x = p1.x + p2.x;

        n.y = p1.y + p2.y;

        return n;

      }

      int main(int argc,char **argv)

      {

        Point p1(1,2);

        Pont p2(2,3);

        Point sum = p1+p2

      }

      来实现Point p(1,2);p++;++p;

      注意:下面两个需要在类中声明为友员,目的是为了访问类的私有成员,如果成员是公有的,则不需要声明为friend;

      Point operator++(Point &p)//++p,这里的重载函数返回了个Point对象,返回的时候会根据p值来调用构造函数Point(const Point &p)来构造一个对象,如果不使用这个返回的对象,立马会调用其析构函数并且被销毁;这样一个构造和析构过程比较浪费资源,如果把返回值声明为Point& operator++(Point &p),这样就不会新声明一个对象了,直接返回传入的p的引用,但需要注意返回引用和返回值的时候不能影响程序的执行结果

      {

        p.x += 1;

        p.y += 1;

        return p;

      }

      Point operator++(Point &p,int a)//p++,在main中也可以通过operator++(p,0)来调用

      {

        Point n;

        n=p

        p.x += 1;

        p.y += 1;

        return n;

      }

      使用的时候p++;++p就可以

      

      对cout的重载,eg:Point m,n;cout<<m<<n;//cout<<m返回的就是cout,是对cout的引用这样才能cout<n;

      ostream& operator<<(ostream &o,Point p)//这个函数也可以在main中通过“operator<<(cout,p)”调用

      {

        cout<<"("<<p.x<<","<<p.y<<")";//<<endl这里的endl表示回车

        return o;

      }

    7、操作符重载-通过类内函数实现

      把上面6中这些外部重载的函数移到类内部就可以,通过减少参数,因为类内部函数在被对象调用的时候,这个对象就是一个参数eg:p1.operator+(p2)

      现在执行m = p1+p2表示为m=p1.operator+(p1);

      注意:cout输出不能在类内部实现,因为"p.operator<<"其第一个参数是Point类型,而函数的第一个参数数ostream类型

      重载“=”:Person& operator=(const Person& p)//如果不重载“=”,那么p = p1,会是的p里面的变量会和p1里面的变量指向同一片内存(注意:Point p=p1执行的是拷贝构造函数)

          {

            if(this == &p)

              return *this;

            this->age = p.age;

            if(this->name){

              delete this->name;

            }

            if(this->work){

              delete this->work;

            }

            this->name = new char[strlen(per.name)+1];

            strcpy(this->name,per.name);

            this->work = new char[strlen(per.work)+1];

            strcpy(this->work,per.work);

            return *this;

          }

    8、访问控制和继承

      class Person{};

      calss Student:public Person{};//Student类继承Person类

      基类成员在派生类中的访问控制属性

           基类访问属性  public                protected                 private

      继承类型

      public          public           protected      隔离

      protected        protected       protected       隔离

      private          private        private       隔离

      (无论那种继承方式,在派生类内部使用父类时并无发别;仅影响外部代码对派生类的使用和派生类的之类)

      说明:1、派生类不能访问基类的私有成员;

         2、派生类可以通过protected或者public的成员函数访问私有成员;

           3、派生类可以访问protected成员,其他外部代码不可以(protected成员外界不可访问);

         4、派生类继承到的成员可以修改成员的权限(protected可以修改为public、private,但是private成员不能被修改权限)

          (eg:在派生类中通过“public:

                        using 父类名::父类成员名或者函数名”可以修改成员变量和函数的权限)

        Base &b2=d1;// 子类对象当父类对象

       b2.print(); // 调用父类函数

     

    9、多重继承

      派生类(子类)有多个父类(基类)

      eg:

      class Sofa{};

      class Bed{};

      class Sofabed:public Sofa,public Bed{};//这里如果不写继承方法,则默认的是private继承

      

      虚拟继承:D继承A和C,这个时候在D中怎么访问A和C同名的函数呢:1、d.A::同名函数();2、把A和C中同名的部分抽象出来一个类B,A和C通过virtual来虚继承,对于虚继承的A和C,在子类D中只会有一份B中的成员(class A:virual public B)

     

    10、构造顺序

      A、先父类(基类)后子类(派生类)

      B、对于父类:先虚拟基类后一般基类

      C、对于子类:先对象成员,后自己的构造函数

     

    11、多态(使用相同的调用方法,对于不同的对象会调用不同的类里面的实现的函数,根据传入的对象类型自己识别该类型,并调用类型的函数)

      eg:反面例子,都是调用父类的eating函数

      class Human{

      public:

        void eating(void){cout<<"use hand to eat"<<endl;}

      }

      class Englishman:public Human{

      public:

        void eating(void){cout<<"use knife to eat"<<endl;}

      }

      class Chinese:public Human{

      public:

        void eating(void){cout<<"use chopsticks to eat"<<endl;}

      }

      void test_eating(Human & h)

      {

        h.eating();

      }

      int main(int argc,char **argv)

      {

        Human h;

        Englishman e;

        Chinese c;

        test_eating(h);

        test_eating(e);

        test_eating(c);

      }

      执行结果:

      use hand to eat

      use hand to eat

      use hand to eat

      

      修改test_eating函数让其能够自己判断传入的参数是属于那个类,并调用该类的eating函数(通过虚函数)

      class Human{

      public:

        virtual void eating(void){cout<<"use hand to eat"<<endl;}

      }

      class Englishman:public Human{

      public:

        virtual  void eating(void){cout<<"use knife to eat"<<endl;}//这里的virtual可以不用写,因为父类是virtual,其子类覆盖后也是virtual函数

      }

      class Chinese:public Human{

      public:

        virtual  void eating(void){cout<<"use chopsticks to eat"<<endl;}//这里的virtual可以不用写,因为父类是virtual,其子类覆盖后也是virtual函数

      }

      //void test_eating(Human * h)

      void test_eating(Human & h)//使用指针或者引用来使用对象时,才有多态,如果是test_eating(Human h)则即使使用虚函数也是调用父类的eating,因为这个时候如果调用的是test_eating(e),这个e被强制转换成Human类,会把对象里面那个指向自己虚函数表的那个指针丢掉

      {

        h.eating();

      }

      int main(int argc,char **argv)

      {

        Human h;

        Englishman e;

        Chinese c;

        test_eating(h);

        test_eating(e);

        test_eating(c);

      }

      执行结果:

      use hand to eat

      use knife to eat

      use chopsticks to eat

     说明:对于非虚函数,在编译时已经确定好调用的是那个类的函数;对于虚函数,在运行是才能确定

        对于有虚函数的类,其对象里面有个指针,指向虚函数表(虚函数表里面也是一些地址,指向虚函数);调用虚函数的时候,是通过这个指针来调用虚函数

        (通过sizeof(对象),可以发现有virtual函数和无virtual函数的对象大小不一样)(虚函数表里面出来有虚函数地址外,还有对象所属类和基类的相关信息)

      静态成员函数、内联函数、构造函数都不能是虚函数,析构函数一般都声明为虚函数(举例如下)

      int main(int argc,char **argv)

      {

        Human* h =new Human;

        Englishman* e = new Englishman;

        Chinese *c = new Chinese;

        

        Human *p[3] = {h,e,c};

        int i;

        for(i = 0;i<3;i++)

        {

          p[i]->eating;

          delete p[i];

        }

      }

      如果析构函数不是虚函数:执行结果:(在上面的各个Human、Englishman、Chinese的类中添加析构函数)

      use hand to eat

      ~Human()  

      use knife to eat

      ~Human()

      use chopsticks to eat

      ~Human()

      如果析构函数是虚函数:执行结果

      use hand to eat

      ~Human()  

      use knife to eat

      ~Englishman()

      use chopsticks to eat

      ~Chineseman()

      

      重载:函数参数不同,名字相同,不可设为虚函数(当重载函数的返回值是本类的指针或者引用时可以设置为虚函数,能实现多态);

      覆盖:函数名字、函数参数和返回值都相同,可设为虚函数;

    12、类型转换

      隐式类型转换:double d = 100.1;int i =d;//double转int,i = 100;

             char *str = "100.ask";int *p = str;//char * 转为int *

      (隐式类型转换有编译器来实现,其并不一定能猜测出代码的意图,编译的时候会出现warning)

      显示类型转换:A、强制类型转换:double d = 100.1;int i =(int)d;

                       char *str = "100.ask";int *p =(int *) str;

             B、动态转换:dynamic_cast<type-id>(expression):该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*,如果tpe-id是类指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用;

      void test_eating(Human & h)

      {

        Englishman *pe;

        Chinese *pc;

        h.eating();

        if(pe = dynamic_case<Englishman *>(&h))

          cout<<"This human is Englishman"<<endl;

        if(pe = dynamic_case<Chinese*>(&h))//这里的&h是取址

          cout<<"This human is Chinese"<<endl;

      }

      int main(int argc,char **argv)

      {

        Human h;

        Englishman e;

        Chinese c;

        test_eating(h);

        test_eating(e);

        test_eating(c);

      }

      执行结果:

      use hand to eat

      use knife to eat

      This human is Englishman

      use chopsticks to eat

      This human is Chinese

             C、静态转换:static_cast<type-id>(expression)编译器在编译的时候已经决定好怎么转换

      下行转换会存在隐患eg:Englishman *pe = static_case<Englishman *>(&h);编译能成功,下行转换存在风险  但如果使用Englshman类的方法是程序会崩溃

                 Englishman *pe = static_case<Englishman *>(&g);编译不能成功 

                 Chinese*pe = static_case<Chinese*>(&g);编译能成功,没问题,上行转换没问题

                D、使用reinterpret_cast从新解析转换(同c语言风格的强制类型转换):double d = 100.1;int i =reinterpret_cast<int>(d);

             E、通过const_case去掉变量const或者volatile属性:const char *str = "100ask"; char *str2 = const_case<char *>(str);

     

    注意:动态转换用于多态场合,即:必须有虚函数,引用转换的时候需要用到虚函数表里面的类和基类信息来转换

        主要用于类层次间的上行转换(派生类转换成基类)和下行转换(基类转换成派生类),还可以用于类之间的交叉转换

       在类层次间进行上行转换是,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全

      eg:

      class Guangximan:public Chinese{

      public:

        virtual  void eating(void){cout<<"use chopsticks to eat,I come from guangxi"<<endl;}

      }

      void test_eating(Human & h)

      {

        //Englishman& pe = dynamic_case<Englishman&>(h);执行的时候会出错,转换失败,引用没指向一个实体,肯定出错

        Chinese & pc = dynamic_case<Chinese&>(h);

        Guangximan &pg = dynamic_case<Guangximan &>(h);

        h.eating();

      }

      int main(int argc,char **argv)

      {

        Guangximan g;

        test_eating(g);//Guangximan类转换成Human类就是上行转换,进入函数后Human类转换成Chinese和Guangximan 就是下行转换

      }

      

  • 相关阅读:
    git分支操作
    redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
    缓存如果使用不当会造成什么后果?
    在项目中缓存是如何使用的?
    excel poi3.17导出导入
    Mongodb: Sort operation used more than the maximum 33554432 bytes of RAM
    VMware12上安装CentOS7
    校验文件是否是Excel文件
    读后感——《构建之法》第1.2.3章
    操作系统——实验一
  • 原文地址:https://www.cnblogs.com/liusiluandzhangkun/p/9109278.html
Copyright © 2011-2022 走看看