C++基础之面向对象

一、结构化程序设计与面向对象程序设计 1、结构化程序设计在结构化程序设计中,采用自顶向下、逐步求精及模块化的思想,将复杂的大问题层层分解为许多简单的小问题。

一、结构化程序设计与面向对象程序设计

1、结构化程序设计

  • 在结构化程序设计中,采用自顶向下、逐步求精及模块化的思想,将复杂的大问题层层分解为许多简单的小问题。
  • 在编写程序时,使用3种基本控制结构来构造程序。可以说,程序基本上都含有顺序、选择、循环3种基本控制结构,这3种结构到目前为止仍是主要的控制结构。程序以控制结构为单位,只有一个入口和一个出口,基于控制结构可以从前往后地顺序阅读程序,程序的静态描述与执行时的控制流程容易对应,所以可以独立地理解各个部分。结构化程序设计主要强调的是程序的易读性

2、面向对象程序设计的概念和特点

  • 所谓面向对象的程序设计方法,就是使分析、设计和实现一个系统的方法尽可能地接近人们认识一个系统的方法。通常包括3个方面:面向对象的分析、面向对象的设计和面向对象的程序设计
  • 面向对象技术把问题看成是相互作用的事物的集合,也就是对象的集合。对象具有两个特性:一是状态;状态是指对象本身的信息,也称为属性二是行为,行为是对对象的操作。通过对事物的抽象找出同一类对象的共同属性(静态特征)和行为(动态特征),从而得到类的概念。对象是类的一个具象,类是对象的一个抽象

3、面向对象的程序设计有“抽象”“封装”“继承”和“多态”4个基本特点。

  • 抽象:对象是系统中用来描述客观事物的一个实体,如各位员工是员工类的一个个对象。对象的特点包括两个方面:属性和操作。属性指的是描述对象静态特征的数据项,如员工的姓名、职位、薪水等,可以用变量来表示;操作指的是描述对象动态特征(即行为)的函数序列,也称为方法或服务,如员工可以请假、加班,员工还可以获得提拔、加薪等。C++中使用对象名、属性和操作三要素来描述对象
  • 封装:在C++中,通过用户定义的类来支持数据封装和信息隐藏
  • 继承:在C++现有类的基础上可以声明新的类,将一个已有类中的数据和函数保留,并加上自己特殊的数据和函数,从而构成一个新类,这就是继承和复用的思想。原来的类是基类,也称为父类或超类。新类是派生类,也称为子类
  • 多态:多态是指不同种类的对象都具有名称相同的行为,而具体行为的实现方式却有所不同。在一个类或多个类中,可以让多个方法使用同一个名字,从而具有多态性。这是通过函数重载及运算符重载实现的多态

二、类的定义

  1. 字母、数字和下划线的组合,大小写敏感,但不能以数字开头,也不能和系统中使用的关键字完全相同。
  2. 类是具有唯一标识符的实体,就是说类名不能重复。类定义以“;”结束,大括号中的部分称为类体。
  3. 定义类时系统并不为类分配存储空间,而只是把类看作是一种模板或样板。或者说,类可以看作是用户自定义的一种数据类型。在C++98标准下,类中声明的任何成员不能使用auto、extern和register关键字进行修饰。
  4. 类中的成员按功能划分,包括成员变量和成员函数;按访问权限划分,包括公有成员成、私有成员和保护员。
  5. 在C++中还可以定义不是任何类的成员的函数,这样的函数可称为“全局函数”
  6. 成员函数既可以在类体内定义,也可以在类体外定义。如果成员函数定义在类体内部,则默认是内联函数。也可以在类体内部声明函数,并加上inline关键字,然后在类体外给出函数定义,这样的成员函数也是内联函数。
  7. 如果成员函数定义在类体外,则类体内必须要有函数原型,类体外函数定义的前面必须用“类名::”来限定,格式如下:
  8. 返回值类型 类名::成员函数名(参数列表){
      成员函数的函数体
    }
  9. 类名是成员函数所属类的名字,符号::是类作用域运算符,表明它后面的成员函数是属于类名标识的这个类的。返回值类型就是这个成员函数返回值的类型。
  10. 类C中不能定义类C的成员变量,但可以定义类C的指针和引用。
  11. 成员函数并非每个对象各自存有一份。成员函数和普通函数一样,在内存中只有一份,它可以作用于不同的对象,为类中各对象共享。
  12. 通常,因为函数体代码较长,所以在类体内仅给出成员函数的原型,然后在类体外给出对应的函数体。如果函数体定义在类体内,则系统将其视为内联函数。类中定义的成员函数允许重载。

 

 

 三、C++程序结构

一个完整的C++程序包括以下几部分:

  • ✓ —个主函数,可以调用其他函数,但不能被调用,也称为主程序。
  • ✓ 用户定义的任意多个的类及全局函数。
  • ✓ 全局说明。在所有函数和类定义之外的变量说明及函数原型。
  • ✓ 注释。
  • ✓ 头文件。
  • 对于比较大的程序,根据主函数和各用户定义的类及全局函数的功能及相互关系,可以把类及全局函数划分为几个程序文件,包括.cpp文件和.h文件。.cpp文件是源程序文件,.h文件是头文件。
  • 从逻辑关系上看,典型的C++程序的结构包括类的定义、类中成员函数的实现及主函数main。

四、Class 中的访问权限

  • public(公有的): 使用它修饰的类的成员可以在程序的任何地方被访问。
  • private(私有的): 使用它修饰的类的成员仅能在本类内被访问。
  • protected(保护的): 它的作用介于public与private之间,使用它修饰的类的成员能在本类内及子类中被访问。
  • 私有类型的成员在类外是不能访问的,通过公有函数访问的效率比直接访问的效率要低。为了权衡这两方面的因素,C++提供了友元访问方式。只有在类内和在友元函数内才可以访问私有成员。

五、Class 的使用

1、构造函数

  • 为了对对象进行初始化,C++提供了一种称为构造函数的机制,用于对对象进行初始化,实际上是用来为成员变量赋初值的。
  • 构造函数是类中的特殊成员函数,它属于类的一部分。给出类定义时,由程序员编写构造函数。如果程序员没有编写类的任何构造函数,则由系统自动添加一个不带参数的构造函数。
  • 声明对象后,可以使用new运算符为对象进行初始化,此时调用的是对象所属类的构造函数。构造函数的作用是完成对象的初始化工作,用来保证对象的初始状态是确定的。在对象生成时,系统自动调用构造函数,用户在程序中不会直接调用构造函数。
  • 定义一个类时,需要为类定义相应的构造函数。构造函数的函数名与类名相同,没有返回值。一个类的构造函数可以有多个,即构造函数允许重载。同一个类的多个构造函数的参数表一定不能完全相同。
  • 当类中没有定义任何构造函数时,系统会自动添加一个参数表为空、函数体也为空的构造函数,称为默认构造函数。所以任何类都可以保证至少有一个构造函数。如果程序员在程序中已经定义了构造函数,则系统不会再添加默认构造函数。
  • 在声明类的构造函数时可以同时给出函数体,这样的构造函数称为内联构造函数。也可以在类体外给出构造函数的定义。构造函数的声明中,形参的个数可以为0,即参数表为空

构造函数的格式:

构造函数的声明格式如下:类名(形参1, 形参2, …,形参n)
在类体外定义构造函数时通常有三种形式
//形式一:
类名::类名(形参1,形参2,…,形参n):x1(形参1), x2(形参2), …, xn(形参n){}
//形式二:
类名::类名(形参1,形参2,…,形参n){
    x1=形参1;
    x2=形参2;
    ……
    xn=形参n;
}
//形式三:
类名::类名(){
    x1=初始化表达式1;
    x2=初始化表达式2;
    ……
    xn=初始化表达式n;
}

初始化时机:定义类的成员函数、成员对象及友元函数时,均不调用类的构造函数。仅当定义类的对象时,才由系统自动调用类的构造函数

构造函数的使用:

  • C++语言规定,创建类的任何对象时都一定会调用构造函数进行初始化。对象需要占据内存空间,生成对象时,为对象分配的这段内存空间的初始化由构造函数完成。
  • 数组:特别地,如果程序中声明了对象数组,即数组的每个元素都是一个对象,则一定要为对象所属的这个类定义一个无参的构造函数。因为数组中每个元素都需要调用无参的构造函数进行初始化,所以必须要有一个不带参数的构造函数。
class Person{
private:
    int id;
    string name;
public:
    Person();
    Person(int id,string name);
    ~Person();
    void setName(string name);
    string getName();
    void setId(int id);
    int getId();
    static void print(Person *person){
        cout<< "id:" << person->id << " name:" << person->name <<endl;
        delete person;
    }
};
Person::Person(){
    cout<<"无惨构造函数" <<endl;    
}
Person::Person(int id,string name){
    this->id=id;
    this->name=name;
    cout << "有2个参数的构造函数" <<endl;
}
int main(){
    Person pe();
    Person pe2=Person(1,"张三");
}

1、使用new关键字

用new创建对象时返回的是一个对象指针,这个指针指向本类刚创建的这个对象。C++分配给指针的仅仅是存储指针值的空间,而对象所占用的空间分配在堆上。使用new创建的对象,必须用delete来撤销。

    //4、类名 *对象指针名 = new 类名;
    Student *stu4=new Student(3,'D');
    cout<< stu4->getName() <<endl;

类名 &对象引用名 = 对象;

    Student stu3=Student(3,'C');
    Student &str4=stu3;
    cout << str4.getName()<<endl;

类名 *对象指针名 = 对象的地址;

    Student stu3=Student(3,'C');
    Student *str4=&stu3;
    cout << str4->getName()<<endl;

类名 对象数组名[数组大小];

    Student stu3[10];
    Student stu4=Student(3,'C');
    stu3[0]=stu4;

同类型的对象之间可以相互赋值。对象和对象指针都可以用作函数参数。函数的返回值可以是对象或指向对象的指针。

2、 复制构造函数(拷贝构造函数):

复制构造函数是构造函数的一种,也称为拷贝构造函数。它的作用是使用一个已存在的对象去初始化另一个正在创建的对象。例如,类对象间的赋值是由复制构造函数实现的。
复制构造函数只有一个参数,参数类型是本类的引用。复制构造函数的参数可以是const引用,也可以是非const引用。一个类中可以写两个复制构造函数,一个函数的参数是const引用,另一个函数的参数是非const引用。这样,当调用复制构造函数时,既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。
复制构造函数的原型如下:

  • 格式一 A::A(const A&)
  • 格式二 A::A(A &)

自动调用复制构造函数的情况有以下3种:
1)当用一个对象去初始化本类的另一个对象时,会调用复制构造函数。例如,使用下列形式的说明语句时,即会调用复制构造函数。

  • 类名 对象名2(对象名1);
  • 类名 对象名2=对象名1;

2)如果函数F的参数是类A的对象,那么当调用F时,会调用类A的复制构造函数。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
3)如果函数的返回值是类A的对象,那么当函数返回时,会调用类A的复制构造函数。也就是说,作为函数返回值的对象是用复制构造函数初始化的,而调用复制构造函数时的实参,就是retrun语句所返回的对象。
注意,在复制构造函数的参数表中,加上const是更好的做法。这样复制构造函数才能接收常量对象作为参数,即才能以常量对象作为参数去初始化别的对象。

class Person{
    public:
        Person(const Person &person);
        Person(Person &person);
}
Person::Person(const Person &person){
   cout << "const类型 copy 构造函数" << endl;
}
Person::Person(Person &person){
     this->id=person.id;
     this->name=person.name;
     cout << "copy 构造函数" << endl;
}

 3、析构函数:

  • 与构造函数一样,析构函数也是成员函数的一种,它的名字也与类名相同,但要在类名前面加一个“~”字符,以区别于构造函数。析构函数没有参数,也没有返回值。一个类中有且仅有一个析构函数如果程序中没有定义析构函数,则编译器自动生成默认的析构函数。析构函数不可以多于一个,不会有重载的析构函数。默认析构函数的函数体为空。
  • 创建对象时自动调用构造函数,那么,什么时候调用析构函数呢?可想而知,在对象消亡时自动调用析构函数。析构函数的作用是做一些善后处理的工作。例如,如果在创建对象时使用new运算符动态分配了内存空间,则在析构函数中应该使用delete释放掉这部分占用的空间,保证空间可再利用。
  • 当使用new运算符生成对象指针时,自动调用本类的构造函数。使用delete删除这个对象时,首先为这个动态对象调用本类的析构函数,然后再释放这个动态对象占用的内存
  • 如果是创建普通对象:Person p();则无需使用delete p释放否空间,他在程序结束的时候会自动调用析构函数
class Person{
    public:
        ~Person();
}
Person::~Person(){
     cout<< "析构函数" << endl;
}

对于对象数组,要为它的每个元素调用一次构造函数和析构函数。全局对象数组的析构函数在程序结束之前被调用。