C++之面向对象的语法笔记

前言

最近项目在开发涉及到的C++内容相对比较多,整理一下,过程中用到的C++面向对象的语法笔记

正文

知识点的概要

  • C++ 类 & 对象
  • 值传递&引用传递
  • 拷贝构造函数
  • 继承& 多态
  • 友元
  • 模板
  • 类型转换
  • 异常捕获

类& 对象

  • C++类定义:本质上是定义一个数据类型的合集
    类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号;
class Teacher
{
public:
    Teacher(); //构造函数 (malloc)
    Teacher(int age, char * name); //构造函数 (malloc)
    ~Teacher();  //析构函数 释放在构造函数里面动态申请的内存 (free)

    void setAge(int age);
    int getAge();
    void setName(char *name);
    char* getName();

private:
    int age;
    char *name;
};
  • C++对象的定义
    声明类的对象,就像声明基本类型的变量一样
Teacher teacher1;  //无参构造函数,也可以Teacher teacher1 = Teacher();
Teacher teacher2(28, "kevin");  //有参构造函数,创建对象, 也可以Teacher teacher2 = Teacher(28, "kevin");
Teacher *p_teacher = new Teacher(29,"chenyu");  //对象指针,创建可直接new关键字或者下面的方式
Teacher *p_teacher2;
p_teacher2 = &teacher1;
  • 完整的例子
    Teacher.h:
#pragma once
class Teacher
{
public:
    Teacher(); //构造函数 (malloc)
    Teacher(int age, char * name); //构造函数 (malloc)
    ~Teacher();  //析构函数 释放在构造函数里面动态申请的内存 (free)

    void setAge(int age);
    int getAge();
    void setName(char *name);
    char* getName();
private:

    int age;
    char *name;

};

Teacher.cpp:

#include "Teacher.h"
#include <iostream>

//c++ 标准库的命名空间
using namespace std;

Teacher::Teacher()
{
    cout << " Teacher 构造函数  地址:" << this << endl;
}

Teacher::Teacher(int age, char * name) 
{
    cout << " Teacher 构造函数  地址:" << this << endl;
    this->age = age;
    this->name = name;
    cout << " Teacher age :" << age << " name :"<< name << endl;
}


Teacher::~Teacher()
{
    cout << " Teacher 析构函数  地址:" << this << endl;
}


void Teacher::setAge(int age) {
    cout << "Teacher setAge is " << age << endl;
    this->age = age;
}
int Teacher::getAge() {
    return this->age;
}
void Teacher::setName(char *name) {
    cout << " Teacher setName is " << name << endl;
    this->name = name;
}
char* Teacher::getName() {
    return this->name;
}

main.cpp:

#include "Teacher.h"
#include "Student.h"
#include <iostream>

using namespace std;

void fun() {
    Teacher teacher1;  //无参构造函数,也可以Teacher teacher1 = Teacher();
    Teacher teacher2(28, "kevin");  //有参构造函数,创建对象, 也可以Teacher teacher2 = Teacher(28, "kevin");
    Teacher *p_teacher = new Teacher(29,"chenyu");  //对象指针,创建是需要new关键字

    teacher1.setAge(31);
    teacher1.setName("jason");

    cout << "teacher1 name: " << teacher1.getName() << endl;
    cout << "teacher1 age: " << teacher1.getAge() << endl;

    cout << "teacher2 name: " << teacher2.getName() << endl;
    cout << "teacher2 age: " << teacher2.getAge() << endl;

    cout << "p_teacher name: " << p_teacher->getName() << endl;
    cout << "p_teacher age: " << p_teacher->getAge() << endl;

    //及时释放
    delete p_teacher;
    p_teacher = nullptr;

}

void main() {
    fun();
    system("pause");
}

值传递&引用传递

在函数调用中,如果需要将类的对象作为形参,传入到某个函数进行调用,这种情况分为值传递和引用传递。

  • 值传递
//为什么这里只调用了析构函数,没有调用构造函数?
//值传递 存在值拷贝
void funValuePass(Teacher teacher) 
{
    teacher.setAge(31);
    teacher.setName("liuyan"); 

    cout << "funValuePass teacher name: " << teacher.getName() << endl;
    cout << "funValuePass teacher age: " << teacher.getAge() << endl;
}
  • 引用传递
    什么是引用?

引用是原变量的一个别名.
n 相当于m 的别名(绰号),对n 的任何操作就是对m 的操作。例如有个人叫是JackMa,绰号是“马爸爸”。说“马爸爸”怎么怎么的,其实就是在说JackMa。所以n 既不是m 的拷贝,也不是指向m 的指针,其实n就是m 它自己。
c++中的引用 (就是再堆中分配空间)

堆空间内存分配.jpg

堆(heap)空间:
动态分配的内存(malloc 等api 分配在堆空间, c++中的new)
堆用于存放进程运行时动态分配的内存段,可动态扩张或缩减。堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。
栈(stack)空间:
局部变量、函数参数、返回地址等(系统自动分配的临时内存)Linux有专门的寄存器管理栈内存(效率高)
.bss段
未初始化或初值为0的全局变量和静态局部变量
.data段
已初始化且初值非0的全局变量和静态局部变量
.text段
可执行代码、只读变量,全局静态变量

//引用传递
//c++ 引用 是内存空间的别名 不存在拷贝 只传递内存别名
void funRefPass(Teacher &teacher) {

    teacher.setAge(32);
    teacher.setName("Chen"); 

    cout << "funRefPass teacher name: " << teacher.getName() << endl;
    cout << "funRefPass teacher age: " << teacher.getAge() << endl;
}
  • 引用和指针的区别
  1. 指针是一个实体,而引用仅是个别名;
  2. 引用使用时无需解引用(*),指针需要解引用;
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 引用“从一而终” 。
  4. 引用没有 const,指针有 const,const 的指针不可变;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
  7. 引用是面向对象的概念,指针是面向过程种的概念,C++能够混编C语言,所以C++支持指针
  • 代码调用
    值引用:
void main() {

    Teacher teacher;

    teacher.setAge(24);
    teacher.setName("lily");
        //值传递
    funValuePass(teacher);
        //这边值没有发生变化
    cout << "teacher name: " << teacher.getName() << endl;
    cout << "teacher age: " << teacher.getAge() << endl; 
    system("pause");
}

输出结果:


值传递的结果.png

引用传递:

void main() {

    Teacher teacher;

    teacher.setAge(24);
    teacher.setName("lily");
        //引用传递
    funRefPass(teacher);
       //这边值已经发生变化
    cout << "teacher name: " << teacher.getName() << endl;
    cout << "teacher age: " << teacher.getAge() << endl; */
    system("pause");
}

输出结果:


引用传递的结果.png

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
拷贝构造函数通常用于:

  1. 通过使用另一个同类型的对象来初始化新创建的对象。
  2. 复制对象把它作为参数传递给函数。
  3. 复制对象,并从函数返回这个对象。
    对拷贝,C++ 的String源码,最能体现。
    举个例子区分赋值和拷贝
String  s1("aaa");
String  s2("bbb");
String  str = s1; //这个就是调用了拷贝构造函数,等同String str(s1);
             str  = s2 //这个是调用赋值函数

拷贝分为 浅拷贝深拷贝
浅拷贝:位拷贝,拷贝构造函数,赋值重载,多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏
深拷贝:每个对象共同拥有自己的资源,必须显式提供拷贝构造函数和赋值运算符

  • 浅拷贝
Student::Student(const Student &student) {
  cout << " MyStudent 拷贝构造函数  地址:" << this << endl;
  this->age = student.age;
  this->name = student.name;
  this->teacherName = student.teacherName;
}
  • 深拷贝
Student::Student(const Student &student) {
    cout << " Student 深拷贝构造函数  地址:" << this << endl;
    int len = strlen(student.name);
    this->name = (char *)malloc(len + 1);
    strcpy(this->name, student.name);

    len = strlen(student.teacherName);
    this->teacherName = (char *)malloc(len + 1);

    strcpy(this->teacherName, student.teacherName);
}
  • 完整的例子
    Student.h:
#pragma once
class Student
{
public:
    Student(int age, char *name, char *teacherName);
    ~Student();

/*private:
    //重写默认的拷贝构造函数 这种方案不常用
    MyStudent(const MyStudent &student);*/
    Student::Student(const Student &student);
public:
    int age;
    char *name;
    char *teacherName;

};

Student.cpp:

#include "Student.h"
#include <iostream>

using namespace std;

Student::Student(int age, char *name, char *teacherName)
{
    cout << " Student 构造函数  地址:" << this << endl;
    int len = strlen(name);
    this->name = (char *)malloc(len + 1);
    strcpy(this->name, name);

    len = strlen(teacherName);
    this->teacherName = (char *)malloc(len + 1);

    strcpy(this->teacherName, teacherName);
}


Student::~Student()
{
    cout << " Student 析构函数 地址:" << this << endl;
    free(this->name); //拷贝构造函数释放野指针出错
    free(this->teacherName);
}


//默认拷贝构造函数 实现浅拷贝
/*Student::Student(const Student &student) {

cout << " MyStudent 拷贝构造函数  地址:" << this << endl;
this->age = student.age;
this->name = student.name;
this->teacherName = student.teacherName;

}*/

//覆盖默认的拷贝构造函数 实现深拷贝
Student::Student(const Student &student) {

    cout << " Student 深拷贝构造函数  地址:" << this << endl;
    int len = strlen(student.name);
    this->name = (char *)malloc(len + 1);
    strcpy(this->name, student.name);

    len = strlen(student.teacherName);
    this->teacherName = (char *)malloc(len + 1);

    strcpy(this->teacherName, student.teacherName);

}

main.cpp:

// 1. 浅拷贝
// 2.避免浅拷贝引发野指针问题
//   2.1 深拷贝
//  2.2 私有化拷贝构造函数 
void copyTest() {
    Student student = Student(21, "Jake", "Jhone");
    cout << "setFunX student1 name: " << student.name << endl;
    cout << "setFunX student1 age: " << student.age << endl;
    cout << "setFunX student1 age: " << student.teacherName << endl;
    Student st2 = student;  // 这里也调用了拷贝构造函数
    cout << "setFunX student2 name: " << st2.name << endl;
    cout << "setFunX student2 age: " << st2.age << endl;
    cout << "setFunX student2 age: " << st2.teacherName << endl;
}

void main() {
    copyTest();
    system("pause");
}

继承& 多态

继承

当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
C++ 继承的继承指的就是基类和派生类的关系。

  • 代码:

基类Plane.h

#pragma once
#include <string>

using namespace std;

class Plane
{
public:
    Plane();
    Plane(std::string name, int year);
    ~Plane();

    void fly();
    void land();
    void printf();
protected:
    std::string name;
    int year;
};

基类Plane.cpp

#include "Plane.h"
#include <iostream>

using namespace std;

Plane::Plane() :name("湾流"), year(1991) {
    cout << "Plane  无参构造函数" << name << year << endl;
}

Plane::Plane(std::string name, int year) :name(name), year(year) {
    cout << "Plane 构造函数" << name << year << endl;
}

Plane::~Plane()
{
    cout << "Plane 析构函数" << endl;
}

void Plane::fly() {
    cout << "Plane fly" << endl;
}

void Plane::land() {
    cout << "Plane land" << endl;
}

void Plane::printf() {
    cout << "Plane printf " << "name = " << name << " year = " << year << endl;
}

派生类Jet.h:

#include "Plane.h"

class Jet : public Plane
{
public:
    Jet();
    Jet(std::string name, int year);
    ~Jet();

    void fly();

    void land();
    void test();
};

派生类Jet.cpp:

#include "Jet.h"
#include <iostream>

using namespace std;

Jet::Jet() {
    cout << "Jet  无参构造函数" << name << year << endl;
}

Jet::Jet(std::string name, int year) {
    cout << "Jet  构造函数" << name << year << endl;
}


Jet::~Jet() {
    cout << "Jet 析构函数" << endl;
}

//重写
void Jet::fly() {
    cout << "Jet fly" << endl;
}

void Jet::land() {
    cout << "Jet land" << endl;
}

void Jet::test() {
}

调用类main.cpp:


//1. 先调用父类的构造函数
//2.释放时先调用子类的析构函数

//子类没有 就使用父类的方法
//子类有实现,就是用子类的重写
//父类型的引用 赋值子类型的对象 方法都是父类型中的方法
void funExtends() {
    Jet jet;
    //jet.fly();

    //jet.Plane::fly();
    Plane *pl = &jet;
    pl->fly();

    Plane &p2 = jet;
    p2.fly();
}

void main() {
    funExtends();
    system("pause");
}

输出结果:父类型的引用 赋值子类型的对象 方法都是父类型中的方法


输出结果.png

输出的结果,调用的还是父类的方法,不是子类的方法,上面的情况明显不是我们想要的结果,怎么解决呢?
方法:使用虚函数 让派生类类 重载父类方法
在Plane.h中,将fly和land两个方法添加virtual进行修饰

    virtual void fly();
    virtual void land();

再次输出结果:


期望的结果.png
  • 继承类型
    如下代码:A继承基类B的时候,带着修饰符,继承的修饰符有三种:public、protected、private。
class A : public B{};

我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

继承类型 说明
public 当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
protected 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
private 当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
  • 多继承
    多继承即一个子类可以有多个父类,它继承了多个父类的特性。类似一个人可以有多个师傅;
class A{};
class B{};
class C: public A, public B{};

多态

  • 概念
    多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
    C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
  • 实例解释
    以上面的plane类为例,jet继承了Plane,是plane的派生类,在进行创建一个Copter派生类 继承Plane基类
    Copter.h:
#include "Plane.h"

class Copter : public Plane
{
public:
    Copter();
    Copter(std::string name, int year);
    ~Copter();

    void fly();

    void land();

};

Copter.cpp:

#include "Copter.h"
#include <iostream>

using namespace std;

Copter::Copter() {
    cout << "Copter  无参构造函数" << name << year << endl;
}

Copter::Copter(std::string name, int year) {
    cout << "Copter  构造函数" << name << year << endl;
}

Copter::~Copter() {
    cout << "Copter 析构函数" << endl;
}

//重写
void Copter::fly() {
    cout << "Copter fly" << endl;
}

void Copter::land() {
    cout << "Copter land" << endl;
}

调用

    Plane *plane;
    Jet jet("波音707", 1997);
    Copter copter("绝影", 2005);

    // 存储Jet的地址
    plane = &jet;
    // 调用Jet的函数 fly
    plane->fly();

    // 存储copter的地址
    plane = &copter;
    // 调用Copter的函数 fly
    plane->fly();

编译输出结果:


image.png
  • 分类
    虚函数 (c++多态的基础) 增加程序的扩展性
    动态多态: 程序运行过程中,觉得哪一个函数被调用
    静态多态: 重载(函数名称相同,参数不同,面向对象的特效,c 中不行)

  • 发生动态多态的条件:

  1. 继承
  2. 父类的引用或者指针指向子类的对象
  3. 函数的重写

重载(overload):在相同作用域内,函数名称相同,参数或常量性(const)不同的相关函数称为重载。重载函数之间的区分主要在参数和常量性(const)的不同上,若仅仅是返回值或修饰符 virtual,public/protected/private的不同不被视为重载函数(无法通过编译)。不同参数是指参数的个数或类型不同,而类型不同是指各类型之间不能进行隐身类型转换或不多于一次的用户自定义类型转换(关于类型转换,请参考前文:类型转型(Type Casting))。当调用发生时,编译器在进行重载决议时根据调用所提供的参数来选择最佳匹配的函数。

重写(override):派生类重写基类中同名同参数同返回值的函数(通常是虚函数,这是推荐的做法)。同样重写的函数可以有不同的修饰符virtual,public/protected/private。

  • 纯虚函数(抽象类)
  1. 当一个类具有一个纯虚函数,这个类就是抽象类
  2. 抽象类不能实例化对象
  3. 子类继承抽象类,必须要实现纯虚函数,如果没有,子类也是抽象类
    关于虚函数和纯虚函数的具体:https://www.runoob.com/w3cnote/cpp-virtual-functions.html

友元

  • 概念
    类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。采用friend 关键字 表示友元。
  • 友元的两种表现形式
  1. 友元函数
  2. 友元类
  • 实例
class Girl
{
public:
    //友元函数不能使用this
    friend void modify(Girl *girl, int age);   //声明的友元函数
    friend class Boy;    //声明的友元类

    //friend void Boy::introduce();

    void tell();

private:
    int age = 16;
    string name = "大笑";
};

//girl class的函数实现
void Girl::tell() {

    cout << "age == " << age << endl;
}

class Boy
{
public:
    Boy(Girl girl) {
        this->girl = girl;

    }

    ~Boy() {

    }

    void changGirl() {
        girl.age = 28;
        girl.name = "vava";
    }

    //已经声明为友元函数 可以访问和修改私有属性s
    void introduce() {
        cout << "My Girl friend age : " << girl.age << "name = " << girl.name << endl;
    }

public:
    Girl girl;
};

//外部实现
void modify(Girl *girl, int age) {

    girl->age = age;
}

void main() {
    /*Girl *girl = new Girl();

    modify(girl, 31);

    girl->tell();

    delete girl;*/

    Girl girl;
    Boy boy = Boy(girl);

    boy.introduce();

    boy.changGirl();
    boy.girl.tell();
    boy.introduce();

    system("pause");
}

输出的结果


友元类.png

友元函数.png

模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

  • 表现形式
  1. 模板函数
  2. 模板类
  • 实例
    模板函数
template <typename T>
void myswap(T& a, T& b) {
    T tmp = 0;
    tmp = a;
    a = b;
    b = tmp;
    cout << "myswap T" << endl;
}

void main() {

    int a = 999;
    int b = 666;

    myswap(a,b);
    cout << "a = "<< a << "b = " <<b << endl;

    char c = '9';
    char v = '6';

    myswap(c, v);

    cout << "c = " << c << "v = " << v << endl;

输出结果:


模板函数.png

模板类

template<class  T>
class  A
{
public:
    A(T a) {
        this->a = a;
    }
     ~A() {
     }

protected:
    T a;
};

template <class  T>
class C : public A<T>
{
public:
    
    C(T c, T a) : A<T>(a) {
        this->c = c;
    }

    void tell() {
        cout << "a : " << a << "c " << c << endl;
    }
private:
    T c;
};

void main() {
 C<int>  c(1,2);
 c.tell();
}

输出结果:


模板类.png

类型转换

C++ 常见的几种类型转换

  1. static_cast 普通值类型转换
  2. const_cast 去常量
  3. dynamic_cast 基类和派生类之间的转换
  4. reinterpret_cast 不通用 不常用 函数指针的转换 (一般在Void * 之间转)
    前三种是通用,常用的,第四种属于不通用,不常用
  • 实例
void func(const char c[]) {
    //c[1] = 'a';
    //去掉const 关键字
    char* c_p = const_cast<char *>(c);
    c_p[1] = 'X';
}

class Person {

public:
    virtual void print() {
        cout << "人" << endl;
    }
};

class Man : public Person
{
public:
    void print() {
        cout << "男人" << endl;
    }

    void chasing() {
        cout << "泡妞" << endl;
    }
};

class Woman :public Person
{
public:
    
    void print() {
        cout << "女人" << endl;
    }

    void createBaby() {
        cout << "造小孩" << endl;
    }
};

void funx(Person* obj) {

    //Man *m = (Man *)obj;
    //m->chasing();

    Man *m = dynamic_cast<Man*>(obj);
    if (m != nullptr) {
        m->chasing();
    }
    else {
        cout << "还需要去趟泰国" << endl;
    }
}

void main() {

    int i = 8;
    double d = 9.5;

    //i = (int)d;
    i = static_cast<int> (d);

    char c[] = "hello";
    cout << "c = " << c << endl;
    func(c);
    cout << "c = " << c << endl;

        Man *m = new Man();
    funx(m);

    system("pause");
}

异常捕获

异常是程序在执行期间产生的问题;
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。 类Java的异常。

  1. throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  2. catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  3. try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
  • 实例
void main() {
    try
    {
        int age = 300;
        if (age > 200) {
            throw "xxx";
        }
    }
    catch (int a)
    {
        cout << "int 异常"<<endl;
    }
    catch (char * b) {
        cout << "char b" <<b << endl;
    }
    system("pause");
}

输出结果:


异常.png
  • 定义新的异常
class NullPointerException : public exception {

public:

    NullPointerException(char * msg) :exception(msg) {
        
    }
};

void main() {
    try
    {
        int age = 300;
        if (age > 200) {
            throw NullPointerException("pgone");
        }
    }
    catch (int a)
    {
        cout << "int 异常" << endl;
    }
    catch (char * b) {
        cout << "char b" << b << endl;
    }
    catch (NullPointerException msg) {
        
    }
    system("pause");
}

笔记就做到这了。如果有什么问题欢迎指出。

参考文献

https://www.runoob.com/w3cnote/cpp-virtual-functions.html
https://blog.csdn.net/qq_39477053/article/details/80322260
https://blog.csdn.net/BLUCEJIE/article/details/104474930

推荐阅读更多精彩内容

  • Typescript 基本语法 概念 TypeScript 是一种强类型语言 什么是强类型语言 不允许改变变量的数...
    抽疯的稻草绳阅读 190评论 0 4
  • 1.var 定义的是变量,没有块作用域的概念,可以跨块作用域访问,不能跨函数访问 块级作用域由 { } 包括,if...
    onresize阅读 868评论 0 2
  • 为什么使用 ES6 ? 每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也有一些令人不...
    肖青荣阅读 132评论 0 3
  • 变量类型的判断 1.JS中常见的数据类型分为基本类型(7种):string、boolean、number、unde...
    抽疯的稻草绳阅读 203评论 0 3
  • Java抽象类有构造函数不? Java抽象类有构造函数,如果没有显式书写,编译器会为其提供一个无参的构造函数,因为...
    老叨鱼阅读 64评论 0 2