• C++ note


    主要是为了学习c++的类和对象
     
    内容摘自 c++概述
    http://see.xidian.edu.cn/cpp/biancheng/cpp/rumen_1/
     
    1,变量 
    ,C++中,我们可以在函数体内声明一个静态局部变量(Static Local Variable)。它在函数运行结束后不会消失,并且只有声明它的函数中能够使用它。我们可以在函数体外声明一个变量,它称为全局变量(global variable),在某一层次声明的变量的作用域就终止于该变量所在层次的末尾。在某个函数的同一语法层次内不能声明多个名字相同的变量。在函数体内声明一个静态局部变量(Static Local Variable)。它在函数运行结束后不会消失,并且只有声明它的函数中能够使用它(static int a;)。
    2,逻辑
    if (m!=0 && n/m<1),当m=0时,电脑不会去尝试用n/m了,而是直接跳过整句语句。这样,我们就能够避免除数为零的错误了
    “……?……:……”称为条件操作符,它的运算优先级比逻辑或还低,是目前为止优先级最低的操作符, max=(a>=b)?a:b;
    在switch语句中,我们要记住四个关键词,分别是switch、case、default和break。
    switch(表达式)
    {
       case 常量表达式1:
       {
          语句块1;
          break;
       }
       default:
       {
          语句块n+1;
       }
    }
    cout <<setw(2) <<a <<b;语句中域宽设置仅对a有效,对b无效。
    3,函数
    一旦函数运行结束,那么该函数中声明的参数和变量都将消失。
    return 符合返回值类型的表达式;
    在返回空类型的函数中可以使用return语句,人为地停止函数的运行,也可以不使用return语句,使其运行完所有语句后自然停止。
    4,存储
    在定义默认参数时,必须在函数声明中定义
    void create(int n=100);//在函数声明中定义默认参数
    int size=sizeof(array)/sizeof(int);//数组的大小
    数组作为参数传递给函数的只是数组首元素的地址,函数在需要用到后面元素时再按照这个地址和数组下标去查找
    指针和整数C的加减法是指针向前或向后移动C个对应类型的存储区域
    数组名是指针,但它是一个指针常量。也就是说,不带下标的数组名不能作为左值。
    数组的大小在编译前必须是已知的常量表达式
    不同于数组,结构是按值传递的。也就是说整个结构的内容都复制给了形参,即使某些成员数据是一个数组。使用返回结构体方式实现修改
    5,调试
    用函数声明和定义分离的方式:把所有的声明都放在shape.h中,把所有的定义放在shape.cpp中
    #开头的命令都是编译预处理命令,比如#if、#else、#endif、#ifdef、#ifndef、#undef和#define
    无论这个文件是C++提供的还是自己编写的,使用#include "文件名"命令一定是正确的
    如果包含头文件时写作如#include <iostream>,但是没有using namespace std;必须要使用std名字空间。
    在编译阶段发生的错误称为编译错误(Compile Error),在运行阶段发生的错误称为运行时错误(Runtime Error)。对于编译错误,我们通过检查并修正语法错误来解决;对于运行时错误,我们通过检查并修正语意(程序设计思想)错误来解决
    6,对象
    面向对象(Object Oriented,简称OO)
    对象就是任何我们可以想象出来的具体的物体
    能够抽象地描述某一些具有共性的物体的词称为类(Class)
    类与结构相似,它也是一种由用户自己定义的数据类型;它也可以通过成员数据来刻画一些现实生活中的东西。不同的是,对它的操作并不是通过普通的函数,而是通过类的成员函数来实现的
    定义完一个类之后务必要在最后加上一个分号。
    类中变量在定义或声明时不说明该成员数据(或成员函数)是公有的还是私有的,则默认为私有的。
    class Node//定义一个链表结点类
    {
       public:
       int idata;//数据能够被外部访问
       char cdata;//数据能够被外部访问
       private:
       Node *prior;//前驱结点的存储位置保密
       Node *next;//后继结点的存储位置保密
    };
    如果一个类的某个成员函数是私有的,那么它只能被这个类的其他成员函数调用。
    const这个保留字来保护成员数据不被成员函数改变
    成员函数的写法就是在函数的参数表后面加上一个const,比如:int readi() const;//通过该函数读取idata,但不能改变任何成员数据
    "::"操作符,它表示该函数是属于某一个类的,称为域解析操作符
    定义类成员函数
    //node.h
    class Node//定义一个链表结点类
    {
       public:
       int readi() const;//通过该函数读取idata,但不能改变任何成员数据
       bool set(int i);//重载,通过该函数修改idata
       bool set(char c);// 重载,通过该函数修改cdata
       private:
       int idata;//存储数据保密
       char cdata;//存储数据保密
    };//类定义结束,分号切勿忘记
    int Node::readi() const   //成员函数readi的定义
    {
       return idata;
    }
    bool Node::set(int i)//重载成员函数定义
    {
       idata=i;
       return true;
    }
    bool Node::set(char c)
    {
       cdata=c;
       return true;
    }
    调用成员函数
    //main.cpp
    #include <iostream>
    #include "node.h"//包含我们编写好的链表结点类头文件,必须用双引号
    using namespace std;
    int main()
    {
       Node a;//创建一个链表结点对象a
       a.set(1);//设置idata
       a.set('A');//设置cdata
       cout <<a.readi() <<endl;
       cout <<a.readc() <<endl;
       return 0;
    }
    对象的引用
       Node b;//声明一个结点对象
       Node &a=b;//声明一个引用
       a.set(0);//效果与b.set(0)相同
    对象指针
       Node b;//声明一个结点对象
       Node *a=&b;//声明一个对象指针
       a->set(0);//效果与b.set(0)相同
       a->readi();//效果与b.readi()相同
    对象的初始化的方法,增加初始化函数 void init(type,type);
    构造函数是一种随着对象创建而自动被调用的函数,与类同名的成员函数就是构造函数,它的主要用途是为对象作初始化,它是一个公有的成员函数,并且构造函数没有返回值类型。
    class Node//定义一个链表结点类
    {
       public:
       Node();//构造函数的声明,构造函数是公有的成员函数,没有返回值类型
       private:
       int idata;//存储数据保密
       char cdata;//存储数据保密
    };
    Node::Node() //构造函数的定义
    {
       cout <<"Node constructor is running..." <<endl;//提示构造函数运行
       idata=0;//初始化idata
       cdata='0';//初始化cdata
    }
    带参数的构造函数(构造函数也可以重载)
    Node::Node(int i,char c)//构造函数重载1,默认参数只需要在函数原型中出现
    {
       cout <<"Node constructor is running..." <<endl;
       idata=i;
       cdata=c;
       prior=NULL;
       next=NULL;
    }
     对应于main中的,Node b(8);//创建一个链表结点对象b,调用构造函数重载1,参数c默认为'0'
    在C++中,每个类都有且必须有构造函数。如果用户没有自行编写构造函数,则C++自动提供一个无参数的构造函数,称为默认构造函数。这个默认构造函数不做任何初始化工作。一旦用户编写了构造函数,则这个无参数的默认构造函数就消失了。如果用户还希望能有一个无参数的构造函数,必须自行编写
    深拷贝构造函数是真正意义上的复制了链表a,并且使得链表a和链表b各自独立,互不干扰。这才是自定义拷贝构造函数存在的重要意义
    //拷贝构造  
     CExample(const CExample& C)  
     {  
      a = C.a;  
      cout<<"copy"<<endl;  
     }  
    CExample B = A; // CExample B(A); 也是一样的,这里不是赋值
    析构函数(Destructor)能随着对象的消亡而自动被调用
    对象作为函数参数时,会先调用对象的拷贝构造函数,把参数的值复制给临时对象,函数结束后,临时对象调用析构函数
    构造函数用于初始化对象,拷贝构造函数用于访问创建的对象,用于类向对象中的似有成员传递指
    对象rect1的p和rect2的指针成员p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”
    初始式example
    #include "iostream"
    #include "stdlib.h"
    using namespace std;
     
    class A
    {
    public:
    A(int i,char c);
    void show();
    private:
    int idata;
    char cdata;
    };
    A::A(int i,char c)
    {
    idata=i;
    cdata=c;
    }
    void A::show()
    {
    cout <<"A.show"<<endl<<idata<<endl;
    cout<<cdata<<endl;
    cout<<"A.show end"<<endl;
    }
    class B
    {
    public:
    B(int i,char c);
    void show();
    private:
    int bint;
    A ao;
    A *p;
    char bchar;
    };
    B::B(int i,char c):ao(i,c)
    {
    cout<<"B copy and creat"<<endl;
    cout<<i<<c<<endl;
    p=&ao;
    cout<<"B copy and creat end"<<endl;
     }
    void B::show()
    {
    cout<<"B.show begin"<<endl;
    p->show();//或者ao.show();
    cout<<"B.show end"<<endl;
    }
    int main()
    {
    A a(1,'a');
    a.show();
    B b(2,'b');
    b.show();
    system("pause");
    }
    如果指针指向不是用 new 分配的内存地址,则在该指针上使用 delete 是不合法的。
    const int *pci = new const int(1024);
    动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改。
    由new生成的ptemp,在执行delete之后,还可以对其进行赋值操作,猜想delete 操作删除的是指针指向内存的内容
     
    一个类成员中有其他类对象,可以用以下方式构造(linklist成员中有node类,pucurrent和head分别为node类的对象和对象指针
    方式一
    Linklist::Linklist(int i,char c):head(i,c)//类名::构造函数名(参数表):成员对象名1(参数表),链表类构造函数,调用head对象的构造函数重载1,详见Node.h文件
    {
       cout<<"Linklist constructor is running..."<<endl;
       pcurrent=&head;
    }
    方式二,需要在node类中声明linklist是友元类,friend class Linklist;
    Linklist::Linklist(Linklist &l):head(l.head)
    {
       cout<<"Linklist Deep cloner running..." <<endl;
       pcurrent=&head;
       Node * ptemp1=l.head.next;//直接访问私有成员数据
    while(ptemp1!=NULL)//复制链表
       {
          Node * ptemp2=new Node(ptemp1->idata,ptemp1->cdata,pcurrent,NULL);
          pcurrent->next=ptemp2;
          pcurrent=pcurrent->next;
          ptemp1=ptemp1->next;
    }
     
    一个函数要访问一个或多个对象的私有成员时,我们可以用友元来解决这个问题。
    friend void ShowNode(Node &n);//声明友元函数ShowNode,在节点类中声明
    在main函数中可以使用ShowNode(b);//用友元函数输出b结点的内容
     
    操作符重载,a是复数
    void Complex::operator =(Complex a)
    {
       real=a.real;
       img=a.img;
    }
    Complex Complex::operator +(Complex a)
    {
       Complex temp(a.real+real,a.img+img);
       return temp;
    }
    这样在main函数中,可以进行操作
    Complex a(3,2),b(5,4),c(1,1),d(4,2),temp;
       temp=a+b;
     
    继承的方法,使我们可以用一种已经编写好的类来扩写成一个新的类
    class 子类名:[public,private,protect]父类名;
    private(私有)和protected(保护)都能实现类的封装性。private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。
     
    private是私有继承父类所有的公有、保护成员继承到子类时,类型会发生改变。父类的公有成员在子类中变成了私有成员,父类的保护成员在子类中也变成了私有成员。父类不能在访问子类中成员
     
    class Stack:private Linklist//私有继承链表类
    void Stack::show()
    {
    Show();//用Linklist类的成员函数实现功能
    }
    父类的成员对象是最先构造的,接着是运行父类的构造函数,最后运行子类的构造函数。
    子类的构造函数的参数传递给父类的构造函数
    Stack::Stack(int i,char c):Linklist(i,c)//将子类构造函数的参数传递给父类的构造函数
    {
       cout <<"Stack constructor with parameter is running..." <<endl;
    }
    使用了继承之后,析构函数的运行顺序依然恰好与构造函数的运行顺序相反。
     
    父类指针能否指向子类对象?子类指针能否指向父类对象?
    在公有继承情况下父类的对象指针指向子类对象是允许的。子类的对象指针指向父类是禁止的
    用父类的对象指针指向子类对象,那么这个指针无法使用子类中扩展出的成员。
     
    子父类例子
    如果有一个本科生对象s1和一个学生对象s2,那么显然s1.study()会是学习高等数学和大学英语,s2.study()会是随便学些什么。但是,如果有一个学生类的指针sp,它也能指向本科生对象,这时调用sp->study()会是怎么样的呢?我们发现,即使它指向一个本科生对象,它也只能“随便学些什么”。
     
    不同的子类的同名成员函数有着不同的表现形式,称为多态性
    多态性往往只有在使用对象指针或对象引用时才体现出来。多态性是面向对象的一个标志性特点,没有这个特点,就无法称为面向对象。
     
    设置虚函数的方法为:在成员函数的声明最前面加上保留字virtual。注意,不能把virtual加到成员函数的定义之前,否则会导致编译错误。
     
    virtual void study();//把学习设置为虚函数,父类中
    void student::study()//成员函数定义处没有virtual
    {
       cout <<"随便学些什么。" <<endl;
       return;
    }
    virtual void study();//把学习设置为虚函数,父类的一个子类中class Undergraduate:public student
    void Undergraduate::study()//成员函数定义处没有virtual
    {
       cout <<"学习高等数学和大学英语。" <<endl;
       return;
    }
     virtual void study();//把学习设置为虚函数,父类的一个子类中class Pupil:public student
    void Pupil::study()
    {
       cout <<"学习语数外。" <<endl;
       return;
    }
    main函数中
     Undergraduate s1;
       student s2;
       Pupil s3;
    student *sp
    无论父类对象指针sp指向哪种子类对象,sp->study()的执行结果总是与对应的类相符合的
    要使用虚函数实现多态性,至少要使各个函数的参数格式也完全相同。
    当子类函数和父类参数不同时,会执行父类中的同名成员函数
    编写成员函数的时候,可以把尽可能多的成员函数设置为虚函数。
     
    给析构函数的前面加上保留字virtual, virtual ~Animal();//虚析构函数,父类animal中
    子类中
    Cat::~Cat()
    {
       cout <<"Cat destructor is running..." <<endl;
    }
    main中
    Animal *pa=new Cat(2,1);
       Cat *pc=new Cat(2,4);
    delete pa;
       delete pc;
    结果:
    Cat constructor is running...
    Animal consturctor is running...
    Cat constructor is running...
    Delete pa:
    Cat destructor is running...
    Animal destructor is running...
    Delete pc:
    Cat destructor is running...
    Animal destructor is running...
    子类和父类的析构函数都被执行
    虚函数是为了实现多态,而虚析构函数是为了同时运行父类和子类的析构函数,使资源得以释放。
     
    只能用于被继承而不能直接创建对象的类设置为抽象类(Abstract Class)。
    类中的确存在,但是在父类中无法确定具体实现的成员函数称为纯虚函数。纯虚函数是一种特殊的虚函数,它只有声明,没有具体的定义。抽象类中至少存在一个纯虚函数;存在纯虚函数的类一定是抽象类。
     
    设置纯虚函数之后并不影响多态的实现,但是却将父类变成了抽象类,限制了父类对象的创建,virtual void study()=0;//在父类的声明中通过代码实现,声明study为纯虚函数
     
  • 相关阅读:
    数据结构之双向链表
    数据结构入门之链表(C语言实现)
    机器人操作臂运动学入门一--D-H参数标定
    机器学习--逻辑回归
    python字符串方法的简单使用
    python学习之网页数据获取
    《机器学习实战》学习笔记一K邻近算法
    杂事
    洛谷 P1926 小书童——刷题大军
    洛谷 P1968 美元汇率
  • 原文地址:https://www.cnblogs.com/eiguleo/p/3879135.html
Copyright © 2020-2023  润新知