参考书籍:

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 就是下行转换

  }

  

3、C++快速入门的更多相关文章

  1. Web Api 入门实战 (快速入门+工具使用+不依赖IIS)

    平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...

  2. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  3. 前端开发小白必学技能—非关系数据库又像关系数据库的MongoDB快速入门命令(2)

    今天给大家道个歉,没有及时更新MongoDB快速入门的下篇,最近有点小忙,在此向博友们致歉.下面我将简单地说一下mongdb的一些基本命令以及我们日常开发过程中的一些问题.mongodb可以为我们提供 ...

  4. 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  5. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  6. Mybatis框架 的快速入门

    MyBatis 简介 什么是 MyBatis? MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结果 ...

  7. grunt快速入门

    快速入门 Grunt和 Grunt 插件是通过 npm 安装并管理的,npm是 Node.js 的包管理器. Grunt 0.4.x 必须配合Node.js >= 0.8.0版本使用.:奇数版本 ...

  8. 【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  9. 【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  10. Vue.js 快速入门

    什么是Vue.js vue是法语中视图的意思,Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API.作者是尤雨溪,写下这篇文章时vue.js版本为1.0.7 准备 我推荐 ...

随机推荐

  1. button按钮怎么实现超链接

    button按钮怎么实现超链接 一.总结 1.我的按钮实现超链接是通过button内嵌a标签来实现的 <button class="am-btn am-btn-default am-b ...

  2. ajax的内容

    ajax是什么? 通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新,可以局部刷新而不必整个页面整体刷新. url的简单认识: 进入服务器的三种方式: 1.localhost:端口号 ...

  3. python版 百度签到

    经常玩贴吧,刚学python ,所以自己弄了一个python版的签到程序.自己的东西总是最好的. 登陆模块参考的http://www.crifan.com/emulate_login_website_ ...

  4. IntelliJ IDEA如何导入maven结构的web工程

    第一步:打开一个现有(也可以不打开,直接用import选择Maven类型)的IntelliJ IDEA工程,点击菜单的"File"->"new"-> ...

  5. 20亿与20亿表关联优化方法(超级大表与超级大表join优化方法)

    记得5年前遇到一个SQL.就是一个简单的两表关联.SQL跑了几乎相同一天一夜,这两个表都非常巨大.每一个表都有几十个G.数据量每一个表有20多亿,表的字段也特别多. 相信大家也知道SQL慢在哪里了,单 ...

  6. Google、Mozilla、Qt、LLVM 这几家的规范是明确禁用异常的

    作者:陈硕链接:https://www.zhihu.com/question/22889420/answer/22975569来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...

  7. 给指定的用户无需密码执行 sudo 的权限

    给指定的用户无需密码执行 sudo 的权限 cat /etc/passwd 可以查看所有用户的列表 w 可以查看当前活跃的用户列表 cat /etc/group 查看用户组 cat /etc/pass ...

  8. 嵌入式 Linux应用程序如何读取(修改)芯片寄存器的值

    这一问题来自项目中一个实际的需求:我需要在Linux启动之后,确认我指定的芯片寄存器是否与我在uboot的配置一致. 举个例子:寄存器地址:0x20000010负责对DDR2的时序配置,该寄存器是在u ...

  9. IIS特殊字符设置

    简介:[iis7]请求筛选模块被配置为拒绝包含双重转义序列的请求.HTTP 错误 404.11 - Not Found 特殊字符最好替换成其他的字符,主要的特殊字符有”*”.”&”.”%”.” ...

  10. 洛谷P1143 进制转换

    题目描述 请你编一程序实现两种不同进制之间的数据转换. 输入输出格式 输入格式: 输入数据共有三行,第一行是一个正整数,表示需要转换的数的进制n(2≤n≤16),第二行是一个n进制数,若n>10 ...