@future

C++面向对象

2023-12-08  本文已影响0人  不返y

二、C++面向对象

c++内存的四个区域

1.代码区

存放函数体的二进制的代码,由操作系统管理,两个特点 共享 可读。

2.全局区

存放全局变量、静态变量以及常量。

常量包括const修饰的全局常量和字符串常量

(全局变量和局部变量不会放在同一个内存区域中)

(全局变量和静态变量放在同一个内存区域中)

3.栈区

由编译器自动分配,存放函数参数d,局部变量,

注意:不要返回局部变量的地址。函数返回局部变量地址后第一次使用可以正常使用,这是因为编译器做了保留,第二次就不能调用了。(不要返回局部变量地址)

4.堆区

由程序员分配,也由程序员释放,如果程序员不释放,会在程序结束后由操作系统自动回收。

4.1 在堆中开辟空间new,如:

int * p = new int(10);

4.2 释放内存delete

delete p; --p是指向堆中内存的指针。

如果p是一个数组指针 ,使用delete[] p;

引用(相当于起 别名

1.引用格式&

int b = 10;
int& a = b;

等同于a和b指向同一块空间。

2.注意事项

  1. 引用必须初始化
  2. 引用定义后,不可修改引用

3.用处

  1. 引用传递&,相当于指针传递;代码更加简洁
#include <iostream>
using namespace std;
//交换两个数字
void function(int& a,int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
int main()
{
    int a = 1;
    int b = 2;
    function(a,b);
    cout << a << b << endl;
}
  1. 返回引用
#include <iostream>
using namespace std;
//把b的值加到a上并返回a
int& add(int& a, int& b)
{
    a = a + b;
    return a;
}
int main()
{
    int a = 1;
    int b = 2;
    int& temp = add(a, b);
    cout << temp << endl;
}

注意:不要返回局部变量的引用(局部变量出了作用域会自动释放)

下面的代码虽然可以的得到正确的结果,但是他是不安全的,因为它访问了理论上已经被释放的变量temp。在部分编译器上可能无法得到正确运行。。

#include <iostream>
using namespace std;
//返回局部变量
int& function()
{
    int temp = 1;
    return temp;//不安全的,不要这样写
}
int main()
{
    int& temp = function();
    cout << temp << endl;
    cout << temp << endl;
}

函数返回引用后,函数可以作为左值,即就当成是个变量。可进行如下的赋值操作,如:

#include <iostream>
using namespace std;
int& function(int& a)
{
    a = 1;
    return a;
}
int main()
{
    int a = 0;
    function(a) = 2;  //相当于有 a = 2;
    cout << a << endl;
}

程序执行结果为2。

4.引用的底层原理

引用传递的底层实际上是一个指针,其取地址和解引用操作是自动的。方便写程序。

int&引用的实际类型为int* const ref指针常量,这也是为什么引用不能修改的原因,因为指针的指向不可改变。

特殊写法(引用常量):

const int& a = 123;

解释:实际上就相当于 是

int temp = 123;

const int& ref = temp

这两行代码

函数

1.函数默认参数

参数有默认值时允许不传值,如果传了值,就不使用默认使用传递的及具体值。

基础代码(不用默认值)

//返回三个数的和
int add(int a, int b,int c)
{
    return a + b + c;
}
int main()
{
    int re = add(1, 2, 3);
    cout << re << endl;
    system("pause");
    return 0;
}

使用默认值的代码

int add(int a, int b = 2,int c = 3) //使用了默认值b默认为2,c默认为3.
{
    return a + b + c;
}
int main()
{
    int re = add(1);  //此处调用,实际相当于add(1,2,3);
    cout << re << endl;
    system("pause");
    return 0;
}

书写规则,有默认值得参数一定要写在未提供默认值的参数后。

也可以在声明里面写默认,但是和函数定义时的默认值不能重复(声明时写了,定义时就不能写)

    int add(int, int = 1, int = 2);//后面的占位参数写法
    int main()
    {
        int re = add(1);
        cout << re << endl;
        system("pause");
        return 0;
    }
    int add(int a, int b, int c)
    {
        return a + b + c;
    }

2.函数的占位参数

占位参数可以只写类型。目前了解,暂不涉及。

如上个代码add函数的参数就是占位参数,而且占位参数也可以有默认参数。

3.函数重载

条件:

  1. 在一个作用域下
  2. 函数名一样
  3. 形参不一样(要么个数不一样,要么类型不一样,要么类型的顺序不一样)

注意:对返回值没有要求。

易错点:

函数重载时尽量不要加默认参数,否则调用时可能出现二义性。尽量避免。

类class

1.使用

    const double PI = 3.1415926;

    //定义圆Circle类
    class Circle 
    {
    public:  //声明下列成员的权限为public
        double m_r;
        double getZC() {
            return 2 * m_r * PI;
        }
    };

    int main()
    {
        Circle cir; //创建圆类的对象
        cir.m_r = 2;   //给内部成员变量r半径赋值
        double re = cir.getZC();  //调用内部成员函数
        cout << re << endl;
        system("pause ");
        return 0;
    }

2.修饰符

也是三种

public private protect

关于struct与class的区,他们其实差不多,不同点也就是默认权限

struct默认的权限public

class默认的权限private

3.初始化与清理

构造函数与析构函数

构造函数就是初始化的函数;析构函数是对象被释放时调用的函数(通常用来释放内部的其他内存,如堆上的未释放空间)。

3.1 构造函数

3.1.1语法
类名 ()
{
    //内容
}

//如:
Circle()//圆的空构造函数
{
    
}

函数名就是类名,没有返回值,也不写返回值。

可以有参数,可以重载构造函数,程序创建对象时一定会调用构造函数,不用手动调用。

3.1.2 析构函数

~类名 (){}

与构造函数基本一致,不同的是它没有参数。只会在对象销毁的时候哦调用。只需在构造函数前加~.

4.构造函数

4.1 类型

  1. 按有无参数分

    1. 无参
    2. 有参
  2. 按构造方式分

    1. 普通构造

    2. 拷贝构造

      拷贝构造函数自动调用的时机:

      1. 以传参的方式把对象传给函数形参
      2. 以函数返回值的形式自动返回拷贝对象
    const double PI = 3.1415926;
    class Circle 
    {
    public:
        
        double m_r;//成员变量习惯上加 m_ 以区分。

        //无参构造
        Circle() {}

        
        //有参构造
        Circle(int r) 
        {
            this->m_r = r;    
        }
        
        //拷贝构造函数
        Circle(const Circle& circle) {
            this->m_r = circle.m_r;
        }
        
        double getZC() {
            return 2 * this->m_r * PI;
        }
    };

4.2 调用构造函数

三种方法,括号,显式,隐式,分别如下:

const double PI = 3.1415926;
    class Circle 
    {
    public:
        
        double m_r;//成员变量习惯上加 m_ 以区分。

        //无参构造
        Circle() {}

        
        //有参构造
        Circle(int r) 
        {
            this->m_r = r;    
        }
        
        //拷贝构造函数
        Circle(const Circle& circle) {
            this->m_r = circle.m_r;
        }
        
        double getZC() {
            return 2 * this->m_r * PI;
        }
    };
    int main()
    {
        //1.括号
        Circle cir11;//无参
        cir11.m_r = 11;

        Circle cir12(12);//有参
        Circle cir13(cir11);//拷贝

        //2.显式
        Circle cir21 =  Circle();
        cir21.m_r = 21;

        Circle cir22 = Circle(22);
        Circle cir23 = Circle(cir21);

        //3.隐式
        Circle cir32 = 32;
        Circle cir33 = cir32;

        cout << cir11.m_r << " ";
        cout << cir12.m_r << " ";
        cout << cir13.m_r << endl;
        cout << cir21.m_r << " ";
        cout << cir22.m_r << " ";
        cout << cir23.m_r << endl;
        cout << cir32.m_r << " ";
        cout << cir33.m_r << endl;
        system("pause");
        return 0;
    }

注:对于Circle(10);这行代码,没有用变量接收,属于一个匿名对象。特点:执行完当前行后立刻释放

4.3默认实现构造函数

默认提供的函数实现的功能

无参构造(空);

有参构造(所有值的赋值);

拷贝构造(所有值的拷贝)(浅拷贝,深拷贝需要自己实现);

不提供默认函数的规则:

  1. 如果提供有参构造,编译器不再提供无参构造;、
  2. 如果提供拷贝构造,编译器不再任何构造;

4.4初始化列表

另一种对象初始化方式。

#include <iostream>
using namespace std;
#include<string>
class student
{
public:
    string m_name;
    int m_age;
    string m_gender;
    student() :m_name("未知"), m_age(0), m_gender("未知")//默认初始化序列表
    {

    }
    student(string name, int age, string gender) :m_name(name), m_age(age), m_gender(gender) //有参初始化列表
    {
    
    }
    void print()
    {
        cout << "name: " << m_name << ",age: " << m_age << ",gender: " << m_gender << endl;
    }
};
int main()
{
    student stu;
    stu.print();
}

5.对象内的成员对象

对象内的一个成员变量是另一个对象,则这个对象称为成员对象

构造·有成员对象的·对象时,会先去构建自身的成员对象,再去完成构造自己。(构造函数调用的先后顺序)

销毁会先销毁成员变量,再销毁自己。(析构函数调用的先后顺序)

6.静态成员

6.1 静态成员变量

所有对象共享同一内存(共享静态成员变量);

一般可以类内定义,类外初始化。(注:初始化静态成员变量不受权限限制)

用类名访问的方式:int 类名::静态成员变量

6.2 静态成员函数

静态成员函数只能访问静态成员变量。也可以通过类名直接访问。

7.对象的内存

c++会给每个空对象1个字节的空间为了区分,其占用的内存位置

关于对象的内存,(记忆:共享的数据不属于对象的空间,如函数,和静态变量)

class student
{
    int m_A;//成员变量,属于类的对象上的空间
    static int m_B;//静态成员变量,不属于类的对象上的空间
    void func1()// 成员函数,不属于类的对象上的空间
    {

    }
    static void func2()//静态成员函数,不属于类的对象上的空间
    {

    }
};

结论:只有非静态成员变量才会占用对象的内存。

8.this指针

非静态函数内都有一个this指针,指向调用函数的对象地址。

作用:

  1. 可以使用this->成员变量访问类内的成员变量。
  2. 可以使用return *this;返回调用对象自己,方便链式编程。

c++编写成员变量习惯,成员变量写m_名字,用于区分

c++中,空指针可以访问成员方法,但是如果成员方法里使用了this或者访问了空指针的内部成员变量,就会出现错误。

可以再成员函数内判断if(this==NULL)return;

提高代码的健壮性

9.const修饰对象成员

9.1 修饰成员函数

const修饰的成员函数称为常函数

特点:常函数不可以修改成员函数的值,除非成员函数声明时加了mutable关键字。 如mutable int m_A;

9.2 修饰成员变量

const修饰的成员函数称为常变量

特点:常变量不能被修改,只能调用·常函数。

10.friend友元(让其他元素可以访问类内私有的部分)

  1. 全局函数做友元(好朋友,可以访问私有内容)

可以使全局函数访问类中的私有内容

#include <iostream>
using namespace std;

class Bank_Card
{
    friend void function(Bank_Card* bc);//使全局函数void function()可以访问类中的私有内容int password
public:
    Bank_Card(int password)  //有参构造函数
    {
        this->m_password = password;
    }
private:
    int m_password;//私有成员变量
};
void function(Bank_Card* bc)
{
    cout << bc->m_password << endl;  //可以得到结果123456
}
int main()
{
    Bank_Card bc(123456);

    function(&bc);
    
    system("pause");
    return 0;
}
  1. 类做友元

可以使另一个类访问该类的私有内容。

#include <iostream>
using namespace std;

class Bank_Card
{
    friend class Person;//使Person类可以访问类中的私有内容int password
public:
    Bank_Card(int password)  //有参构造函数
    {
        this->m_password = password;
    }
private:
    int m_password;//私有成员变量
};

class Person
{
public:
    void getPassword(Bank_Card* bc)
    {
        cout << bc->m_password << endl;  //可以得到结果123456
    }
};

int main()
{
    Bank_Card bc(123456);

    Person person;

    person.getPassword(&bc); //通过person访问Bank_Card中的私有成员password
    
    system("pause");
    return 0;
}
  1. 成员函数做友元

可以使成员函数访问类中的私有内容(很难,因为c是按照顺序从上到下执行的,很多事情需要声明, 要注意顺序)

#include <iostream>
using namespace std;

class Bank_Card; //1.先声明Bank_Card类,让Person类知道有这个类,不至于报错

class Person
{
public:
    void getPassword(Bank_Card* bc);  //2.再声明Person中的getPassword函数,让Bank_Card知道他的存在
};

class Bank_Card
{
    friend void Person::getPassword(Bank_Card* bc);//3.使Person中的getPassword函数可以访问类中的私有内容int password
public:
    Bank_Card(int password)  //有参构造函数
    {
        this->m_password = password;
    }
private:
    int m_password;//私有成员变量
};



void Person::getPassword(Bank_Card* bc)
{
    cout << bc->m_password << endl;  //4.最后再实现getPassword内容,确保实现的时候已经定义过Bank_Card中的m_password成员变量
}

int main()
{
    Bank_Card bc(123456);

    Person person;

    person.getPassword(&bc);
    
    system("pause");
    return 0;
}

11.在类外创建成员函数

类名 :: 函数和初始化函数的格式一致。

运算符重载

1.加法+运算符

函数的名字可以写成operator+

这样可以实现 + 运算符 的重载,

也可以在类内部的成员变量实现,相当于给类之间的运算增加重载。

本质上还是调用函数,只不过是有一种简写的形式。

1.1 类内重载

#include <iostream>
using namespace std;


class Number {
public:
    int m_a;
    int m_b;

    Number() {}

    Number(int a, int b) {
        m_a = a;
        m_b = b;
    }

    //加法+的重载,类内
    Number operator+ (Number& number) {
        return Number(number.m_a + m_a, number.m_b + m_b);
    }

};
int main()
{
    Number n1(1, 2);
    Number n2(3, 4);
    Number n3 = n1 + n2;
    cout << n3.m_a << endl; //结果为: 4
    cout << n3.m_b << endl; //结果为: 6
    system("pause");
    return 0;
}

1.2函数外的重载

#include <iostream>
using namespace std; 


class Number {
    public:
    int m_a;
    int m_b;

    Number(){}

    Number(int a, int b) {
        m_a = a;
        m_b = b;
    }
};

//类外的重载函数
Number operator+ (Number& n1,Number& n2) {
    return Number(n1.m_a + n2.m_a, n1.m_b + n2.m_b);
}

int main()
{
    Number n1(1, 2);
    Number n2(3, 4);
    Number n3 = n1 + n2;
    cout << n3.m_a << endl; //结果为: 4
    cout << n3.m_b << endl; //结果为: 6
    system("pause");
    return 0;
}

注:1。内置的数据类型运算是不可更改的。2.不要滥用重载。

2.左移<<运算符重载

一般用来指定自定义类的输出格式,一般不写在类内,即不用成员函数写,而用全局函数如:

ostream& operator << (ostream& cout,Number& n) 
{
    cout << "number.m_a = " << n.m_a << ",number.m_b = " << n.m_b;
    return cout;
}

注意,如果类中的变量是私有的可以使用友元去访问。

3.递增++运算符重载

#include <iostream>
using namespace std;

class Number {
public:
    int m_a;
    int m_b;

    Number(int a, int b) {
        m_a = a;
        m_b = b;
    }

    Number(Number& n)
    {
        this->m_a = n.m_a;
        this->m_b = n.m_b;
    }

    //重载前置++运算符, 
    Number& operator++()
    {
        this->m_a++;      //实现内容:两个数字都自增
        this->m_b++;    
        return *this;     //细节:返回引用实现链式编程
    }

    //重载后置++运算符, 
    Number& operator++(int)//占位int,可以认为是后置++的要求写法
    {
        Number temp(*this); //临时存储,因为后置++,返回值为未操作的值(注意,这样实现不支持链式编程)
        this->m_a++;      //实现内容:两个数字都自增
        this->m_b++;
        return *this;     //细节:返回引用实现链式编程
    }
};

int main()
{
    Number n(1, 2);
    ++n;
    cout << n.m_a << endl; //结果为: 2
    cout << n.m_b << endl; //结果为: 3
    n++;
    cout << n.m_a << endl; //结果为: 3
    cout << n.m_b << endl; //结果为: 4
    system("pause");
    return 0;
}

4.赋值运算符=重载

c++的类中实际默认实现有提供4个函数,前三个 构造函数 拷贝构造 析构函数

第四个是=的默认重载,和拷贝函数的默认实现一样。

<span style="color:red">注意:深拷贝时要先判断,源对象在堆中是否有数据,然后再清除,再进行深拷贝。</span>

5.关系运算符== 和!=重载

和前面差不多,返回bool就行。

6.函数调用运算符()重载

因为类似函数调用,所以也叫仿函数

了解:匿名函数对象

下面的Myadd是实现了函数调用(括号)运算符重载的类。MyAdd()为构造对象的函数,后面的(100,100)为调用的括号()重载

cout << MyAdd()(100,100) << endl;

继承

1.继承格式

class 子类 : 继承方法 父类

{

    //内容

}

//如:
//Base是Son继承的父类
class Son :public Base
{
    
    //内容
    
}

继承方法有public、protected、private

public:直接继承所有内容,最低访问权限为public(可以继承但是不能访问父类中的private内容)

protected:继承所有内容,最低访问权限为protected(但是不能访问父类中的private内容)

private:继承所有内容,最低访问权限为private(但是不能访问父类中的private内容)

2.继承相关问题

2.1 父类与子类的构造与析构顺序

子类被创建前,一定会先创建父类对象。析构顺序相反。

即 子类的创建与销毁顺序是:【父类构造】-->【子类构造】-->【子类析构】-->【父类析构】

2.2 子类与父类的成员重名

如果重名,直接调用子类的方法,而不是父类。

如果想调用父类的,需要加上父类的作用域,如:

son是一个Son的对象,Base是Son的父类

son.Base::m_A; //这里访问的就是父类Base里的m_A

如果子类中有与父类中的重名函数,则会将父类中的所有相同名字的成员函数全部隐藏(包括重载)。若想调用则需加作用域。

同名静态成员,操作与普通成员一致。

2.3 继承信息的查看工具

继承信息的查看工具

3.多继承语法

3.1 格式

class 子类 : 继承方法 父类,继承方法 父类,...

{

    //内容

}

//如:
// Base1等 都是Son继承的父类
class Son :public Base1,public Base2,...
{
    
    //内容
    
}

3.2 问题

3.2.1 重名

父类中出现了重名的内容需要使用作用域区分。

3.2.2 菱形继承(重复继承问题)

设有四个类,Class1,Class2,Class3,Class4

其中Class2和Class3继承Class1 , Class4又继承了Class2和Class3 。

这就是菱形继承,常见的重复继承,Class4等于继承了两次Class1。

解决方案:虚继承 ::virtual 内部实际是继承 指针数据 保证数据只有一份。

正常写代码时,不建议使用多继承的操作,仅仅作为了解

格式:

class 子类 :virtual 继承方法 父类

{

    //内容

}

//如:
//Base是Son继承的父类
class Son :virtual public Base 
{
    
    //内容
    
}

多态

1.概念

子类可以使用父类的名字。

2.多态的问题

2.1 调用的函数是父类的还是子类的

静态多态(早绑定)、一定是调用者的函数

动态多态(晚绑定):看调用者的实际类型所重写的函数(和java一致) (使用虚函数:virtual )

动态绑定底层实际也是一个虚函数指针。原理:使用函数指针,从父类到子类的继承就是一个不断覆盖函数指针的过程。

纯虚函数

写法 :

virtual void func() = 0;  //纯虚函数

如果一个类中有一个纯虚函数,则这个类称为抽象类。(类似于java中的抽象函数与抽象类)

特点:

  1. 抽象类无法实例化对象;
  2. 子类如果不重写纯虚函数,则子类也是抽象类。

虚析构和纯虚析构

虚析构和纯虚析构的存在是为了使子类可以释放自己的所有内存。主要是堆上的数据的释放。使用方法和虚函数和纯虚函数基本一致,细节如下:

区别 有纯虚析构无法实例化对象,但是如果是有虚析构可以实例化对象(类内没有其他的虚函数)。

但是纯虚析构一般需要代码实现,可以先声明函数=0;,再在函数外实现函数体。

文件操作

1.准备

进行文件操作的头文件<fstream>

文件类型

  1. 文本文件,以ASSCII码存储
  2. 二进制文件,以二进制存储

操作文件的类型

  1. 读ifstream
  2. 写ofstream
  3. 读写fstream

2.基本操作

  1. 包含头文件

    #include<fstream>

  2. 创建流对象

    流对象

  3. 打开文件

    流对象.open("文件路径",打开方式1|打开方式2);

    也可以在创建时直接传参,两步合一步

    打开方式

注:如果是读,要判断文件是否打开成功。

判断方式流对象.is_open() ,返回值为bool:成功为true,失败为false。

  1. 写或读

    流对象<<流对象>>

    非二进制的读文件的四种方式

    //第一种:
    char buf[1024] = {0};
    while(ifs>>buf)
    {
     cout<<buf<<endl;
    }
    
    //第二种
    char buf[1024] = {0};
    while(ifs.getline(buf,sizeof(buf)))
    {
     cout<<buf<<endl;
    }
    
    //第三种
    string buf;
    while(getline(ifs,buf))
    {
     cout<<buf<<endl;
    }
    
    //第四种
    char c;
    while((c=ifs.get())!=EOF)
    {
     cout<<c;
    }
    cout<<endl;
    

    二进制的读写

    流对象.read

    流对象.write

  2. 关闭文件

    流对象.close();

补充:

#pragma once          //防止头文件重复包含
#include <iostream>       //包含输入输出流头文件
using namespace std;      //使用标准命名空间
上一篇下一篇

猜你喜欢

热点阅读