C++复习专用-类\结构\函数\二维数组\多态简述

类声明

//test1.h     注意这里是头文件
class stock
{
    private:
        std::string company;
        long shares;
        double share_val;
        double total_val;
        void set_tot(){cout<<"fuck"<<endl;
    public:
        void acquire();
        void buy();
        void spell(int a, int b, double sum);
        void output(std::string str, name, int age);
};
  
/*
    这里介绍一下public和private,显然public就是对外开放的,你可以通过类对象用.运算符或通过指针利用->访问符, 来访问公有成员函数,即public下的成员统称#为公有成员,同理private下的成员统称为私有成员
    是的,public成员可以通过.操作来进行访问,但是私有成员无法直接通过对象用.操作符来访问,必须由对象通过公有成员来访问私有成员
    这种私有成员,很好的杜绝了数据外泄,可以很好的组织程序直接访问数据,这被称为数据隐藏
*/

通常C++程序员,把接口(类定义)放在头文件中,如上程序,而类方法的定义则在包含上述头文件的源代码文件中,那类定义和类方法定义是如何具体关联在一起的呢??

  • 接口/封装

类的思想,把公有接口和实现细节分开,把实现细节放在一起并将他们和抽象分开,即术语封装.

上述的数据隐藏也是一种封装,把实现的细节隐藏在私有部分中也是封装.

类的公有接口表示了设计的抽象组件,如上述的public中的很多个void函数就是对数据操作的方法函数,就是从具体的数据中抽象出来的一种数据操作方法,这些个方法函数声明被放在了.h头文件中, 而这种方法的具体算法,即函数体的结构实现却是在含有.h头文件的源代码文件中实现的.这也是一种封装,即把实现细节和抽象分开

类和结构有什么差别吗? 

是的,就目前来看,好像没有什么区别,只不过class是具有public和private的struct,

class和struct最大的、唯一的区别在于, class默认访问的是private而struct默认访问的是public.是的, 结构在C++中进行了扩展,它也可以有class的一些特性了比如public和private

C++通常把类来描述数据和相关操作,而结构仅用来纯粹的表示数据对象

  • 如何实现类的成员函数呢?

类的成员函数/方法函数/方法,和普通的函数一样有函数声明,有返回值,有参数,有函数体,

区别:

1⃣️函数声明被放在了头文件,而函数具体定义被放在含有头文件的源代码中

2⃣️定义函数成员时,需要用作用域解析符  :: 来标出函数所属的类

当然,类方法可以访问类的private成员(这是我们上边说好的)

类的实现一般包含了3个文件:头文件创建类,定义文件实现类函数,主程序文件调用引用类。

//实现类的成员函数,就是在包含了头文件的源代码中去定义函数体
double stock::acquire()   
{
    "在类方法函数的定义中,你可以随便调用任何一个成员变量"
}

float stock::buy()
{
    ...
}

double stock::spell(int a, int b, double sum)
{
    ...
}

long stock::output(std::string str, name, int age)
{
    ...
}

类方法函数的定义和普通函数的区别:

类方法函数不需要进行函数原型声明, 而普通函数需要

类方法函数定义声明时,需要使用::来表示所属的类,普通函数不需要

  • 成员的访问,公有还是私有

OOP的主要目标之一就是对于数据的隐藏,所以组成类接口的成员函数在公有部分,而数据项往往在private部分

为什么成员函数不能放在private部分?因为如果函数在private上就无法通过类对象直接调用成员函数了

如果我非要在类声明中定义一个函数,即类方法的函数体在类声明中了,而且在private中,会怎样?

//类声明,即头文件中
//举例说明类声明
//test1.h     注意这里是头文件
class stock
{
    private:
        std::string company;
        long shares;
        double share_val;
        double total_val;
        void set_tot(){cout<<"fuck"<<endl;}    #就是笔记开头的这个语句
    public:
        void acquire();
        float buy();
        void spell(int a, int b, double sum);
        double output(std::string str, name, int age);
};

说明: 只要是在类声明中定义的函数,都是内联函数即,自动转成inline函数,在类声明中的函数有个特点:短小精悍

扩展思路,我也可以在类声明下边定义一个内联函数,只需要指定对应的类就行了,如下

//举例说明类声明
//test1.h     注意这里是头文件
class stock
{
    ...
};
inline void stock::set_tot()
{
    cout<<"fuck"<<endl;
}
//这和上述代码是一样的效果,不要忘记,这里也是在头文件中哦, 即使inline函数是完整的定义声明(含函数体)

类对象初始化-构造函数

由于类的数据项是隐藏的,所以无法像int类型或者结构那样可以定义时初始化,毕竟数据访问是私有的,不能够直接访问

  • 类构造函数就是为了初始化而产生的,C++提供了一个特殊的成员函数,即类构造函数,专门用于构造新对象,作用就是把具体的值赋给数据成员
    • C++对这种特殊的成员函数提供了 名称和适用语法, 我们需要提供方法定义

      • 这类函数的名称和类的名称相同,即stock类中有个成员函数是stock()

      • 这种特殊的成员函数即构造函数,是没有返回值的

  • 创建这种特殊函数即构造函数,这种函数的参数名不能和类的数据成员名相同,可以加前缀m_等

构造函数的声明

//创建一个构造函数,需要两部分,即声明和定义
//声明
//class.h
stock(const string & co, long n =0, double pr =0.0);
//定义
//project.cpp
stock::stock(const string & co, long n, double pr)
{
  	"在类方法中,你可以随便调用任何一个成员变量"
    company =co;
    if (n < 0)
        {    
            cout<<"fuck"<<endl;
        }
        else {cout<<"gun"<<endl}
}
//构造函数没有返回值, 构造函数名字和类名相同,构造函数的具体定义是你自己来搞的

构造函数的调用

构造函数如何使用呢?怎么避免没有使用类而只是在使用构造函数呢??

  • 显示调用构造函数
stock food = stock("world", 250, 12.2);
//第一个stock是类,创建了对象food,第二个stock是类构造函数
  • 隐式调用构造函数
stcok food("world",250,122.2);
//这里的stock是类,创建了对象food
  • 结合new来动态创建且初始化
stock *food_ptr = new stock("world",250,122.2);
//第一个stock是类,创建了对象指针,第二个stock是类构造函数

注意! 普通的类可以通过对象用.运算符来访问类函数,但是无法访问类构造函数,即构造函数用来创建对象,但不同通过对象来调用

~析构函数

构造函数用来初始化对象,而析构函数用来清理对象,构造函数可通过new来分配内存,,析构函数则用delete完成内存释放,如果没有用new那么析构函数则没有什么任务可做了.

析构函数很特殊,即在类的名字前加~就是它的名字了,如stock类的析构函数是~stock()

和构造函数一样,析构函数没有返回值和声明类型语句,不同的是析构函数也没有参数

析构函数的声明——如何使用析构函数

析构函数若不担任任何重要工作,可以将它写成不执行任何操作的函数

stock::~stock()
{}

析构函数的调用——什么时候用析构函数

编译器决定,通常不会显式调用析构函数

同理,如果我们没有提供析构函数,编译器将自动声明一个析构函数(注意是声明),然后发现有删除对象的代码后,则自动定义一个默认的析构函数(这里才是定义)

构造函数和析构函数都是把声明放在头文件中的,而具体的函数定义是在源代码中的

//stock.h头文件
class stock
{
    private:
        std::string company;
        long share;
        double share_val;
        void set_tot(){cout<<"fuck"<<endl;}
    public:
        stock();
        stock(const std::stirng &co, long n=0, double pr=0.0);
        ~stock();
        ...
}
//stock.cpp源代码
#include <iostream>
#include "stock.h"
stock::stock()
{
    std::cout<<"这里是默认构造函数"<<endl;
    company ="no name";
    shares = 0;
    share_val = 111;
}

stock::stock(const std::stirng &co, long n=0, double pr=0.0);
{
    std::cout<<"普通构造函数的输入字符串是:"<<co<<endl;
    company = co;
    ...
}
stock::~stock()
{
    cout<<"there are nothing"<<endl;#可以不写,只是为了看看
}

可以利用大括号{}进行初始化吗??

stock test1 = {"test1", 100, 2.2};
stock test2 {"test2",200.3.4};
stock test{}

前两个直接调用stock::stock(const std::stirng &co, long n=0, double pr=0.0)

最后一个直接调用stock::stock()

修改类的数据成员

this指针可以帮到你

什么意思呢?就是你的private的数据是只能通过public成员函数来访问的,但无法修改,如何去修改这个数据呢?

类继承

  • C++提供了比修改代码更好的方法来扩展和修改类,那就是类的继承

    • 可以从已有的类派生出新的类,派生类继承了原有的特征,包括全部方法函数

      •  原有的类叫基类, 派生的类叫派生类,或者说继承的类叫做派生类

基类

  • 利用符号  :  来指出RP类的基类是TTP类,加上public表示这是个公有基类,RP作为派生类又叫做公有派生类

  • 派生类的对象会包含基类对象

  • 公有派生类会拥有基类的全部公有成员和私有部分,但基类的私有部分,派生类并不是可以随意访问

    • 派生类继承了基类的实现, 即基类的数据成员

    • 派生类继承了基类的方法,即基类的接口

class TTP
{    
    ...
}
class RP: public TTP
{
    ...
}

派生类

知道了基类和派生类的关系,那我们在派生类的定义中到底需要加什么东西呢???

  • 属于派生类自己的构造函数

  • 追加非基类即额外的数据成员和成员函数

派生类构造函数

派生类不能直接访问基类的私有成员,必须通过基类的方法(函数)进行访问

Q:我们的构造函数是用来对类成员进行初始化的, 无法访问这些数据成员,我们就无法通过派生类的构造函数来进行数据的初始化了,

A:需要你的派生类构造函数必须使用基类的构造函数

基类对象应该在程序进入派生类构造函数之前创建, 要创建派生类对象,你就必须要先创建基类对象

注意这里并不是说要创建两个对象, 而是C++利用了列表初始化语法来完成了这个操作,怎么完成的?看下边

RatedPlayer::RatedPlayer(unsigned int r , const string & fn, const strng & ln, bool ht):TableTennisPlayer(fn, ln, ht)
{
    //派生类  ::   派生类构造函数(参数)  :  基类构造函数(基类参数)
    ...
}
//基类成员初始化列表 :TableTennisPlayer(fn, ln, ht), 这是调用了基类的构造函数

//这里可能晦涩难懂, 举个例子

RatedPlayer xjh(1123, "M", "duck", true);

首先RatedPlayer构造函数, 把xjh对象的参数都给了RatePlayer的构造函数的形参r, fn, in, ht这四个,然后这个形参又作为实参,传递给了TableTennisPlayer的形参fn in ht,于是创建好了一个基类对象,并且你的xjh的参数都存储在了基类对象中,然后, 程序进入了RatedPlayer构造函数的函数体, 执行函数体的语句

创建派生类对象——>派生类构造函数——>对象参数给构造函数的形参——>构造函数形参作为实参给基类构造函数——>基类构造函数创建基类对象——>派生类对象的初始化参数全部存储在该基类对象中——>完成上述创建派生类对象必须创建基类对象的要求

必须首先创建基类对象, 但是像上述代码中, 你不写:TableTennisPlayer(fn, in, ht)这个基类构造函数,程序会自动调用程序

使用派生类

使用派生类,你必须要把基类和派生类的声明放在一起,当然也可以通过#include方法放在不同的.h头文件中, 也可以放在一个头文件中

类的接口定义可以单独放在一个.cpp文件中,只需要你的源代码和这个.cpp文件都包含了上述头文件,就行. 而你的头文件,无需包含源代码和.cpp文件

//tabletenn1.h 头文件
using namespace std;
using namespace string;
class TableTennisPlayer
{
    ...
}
class RatedPlayer : public TableTennisPlayer
{
    ...
}


//tableten1.cpp 接口源文件
#include "tabletenn1.h"
TableTennisPlayer::TableTennisPlayer () {}
//这里都是两个类的函数结构定义程序



//main.cpp 主程序源文件
#include "tabletenn1.h"
int main()
{
    //直接使用类
    TableTennisPlayer xjh1(...);
    RatedPlayer xjh2(...);
}

使用与设计类

学习了定义和简单的使用类,了解了两个成员函数析构函数和构造函数,这些都是类的通用原理,接下来开始学习类的设计技术

创建类对象

注意! 每个创建的对象,都有自己的内存块,这里可以联想普通变量,类是int类型,而对象就是int类型的数据对象,比如int name=0;

#include <iostream>
#include "test1.h"
int main(){
    stock xjh;//创建类对象
    xjh.acquire();
    xjh.buy();
    xjh.spell();
    xjh.output();
// 对于指针
    stock * ptr = new stock;
    ptr->buy();
    ptr->spell();
}

结构

  • 数组允许定义可存储相同类型数据项的变量,结构允许您存储不同类型的数据项。
  • 可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,把 & 运算符放在结构名称的前面
  • 指针访问结构的成员,必须使用 -> 运算符
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
  • 更简单的定义结构的方式,您可以为创建的类型取一个"别名". 现在,您可以直接使用 Books 来定义 Books 类型的变量,而不需要使用 struct 关键字(一般情况下即使不引用也是可以忽略strcut关键字来定义结构对象的)
//previous
struct Books
{
   string name;
   int   book_id;
};

struct Books Book1;
Books newBook;
newBook.name = "abcd";

//now
typedef struct Books
{
   string name;
   int   book_id;
}BOOKS;

BOOKS Book1, Book2;

为了访问结构的成员,我们使用成员访问运算符(.)

struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;


struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

typedef struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
}Books;

函数

函数的构建模板如下

typeName functionName(parametersList);
void mian(){}
typeName functionName(parametersList)
{   
    statements
    return value;
}
//retrun无法返回数组,但可以是结构,类对象,或者可以把数组作为结构和类对象的成员返回
//函数参数也不能是数组

函数无法返回数组,函数参数也不能是数组,但是可以通过结构、类来使用数组,或者在参数重吧

定义函数时参数列表的变量是形参,调用函数时,是实参,实参一直存在(当然这里是普通变量,new则另说),形参,在函数被调用时,计算机为形参分配内存,函数结束时,释放这些内存块,所以形参叫局部变量,他们的作用域仅在该函数内部

typeName fucntion(int param1, &prama2){}
void main()
{
    int a = 10;
    int b =11;
    function(a, &b);
}
//a和b都是实参,param1和param2都是形参

把数组和函数结合在一起

等等!不是说参数不能是数组,且retrun无法返回数组吗???难道只是做参数传递吗??No

int functionArray(int array[], int n);
//这里的array[]实际上是个指针,还记得指针和数组名其实是一个东西吗?
  • 是的, int array[]表示一个数组, 而方括号是空的,则表示可以传递任何长度的数组给函数functionArray(),其实是错误的, 这里的array是指针!!!但是你把他看成数组又何妨呢?

  • 另外,如果你想通过函数来处理数组, 你最好把数组名,数组类型和数组的长度都告诉函数

如果你想让函数可以修改传进来的数组

void function(double arr[], int n){}

如果不想让函数修改传进来的数组

void function(const double arr[], int n){}

内联函数

  • 编译过程最终是生成可执行文件,然后运行,在运行程序时,操作系统将可执行文件,即这些机器语言指令装载到内存中,然后按照指令的地址逐步执行,有时候遇到循环或者条件分支语句时会跳着执行,函数调用也会从一个地址跳到函数所在的首地址,函数结束则返回

  • 当程序跳转到函数地址时,即程序在函数调用后会立即存储该指令的地址,然后把函数参数放到堆栈内存池中,然后直接跳到函标记函数起点的内存块处开始执行函数代码,最后函数结束就跳回去了。

  • 来回的跳跃并记录跳跃位置,需要太多的开销了,因此C++提供了内联函数,无需跳跃,但需要更多内存,即如果程序再10个不同的地方调用同一个内联函数,则这个内敛函数将有10个副本

  • 使用: 函数声明或定义前加关键字inline即可

  • 引用变量

    • 参考笔记

函数重载

  • 术语:多态和重载,指的是同一回事,就是同一物体的多种形式

  • 函数多态=函数重载,即允许函数有多种形式,即有多个同名函数,多态是函数多态,重载其实是函数名称重载

  • 无论多态还是重载,意义就是,名字相同,参数列表不同

  • 函数特征标:函数的参数列表,如果两个函数, 参数数目,参数类型,参数排列顺序,三者都相同,那么他们的特征标相同,显然这里和参数变量的名称无关

  • C++允许定义函数名称相同的函数, 条件就是其特征标不同

double cube(double x);
double cube(double &x);
//看起来特征标是不同的,其实,类型引用和类型本身是同一特征标

double long(double x);
long long long(double x);
//显然这里的long函数是不允许被重载的,因为特征标是相同的,即返回值类型并不是特征标之一

函数重载很棒,但不要滥用,仅在函数执行基本基本相同任务,但对象的数据类型不同时,可以考虑函数重载

函数模板

  • 函数模板是通用的函数描述, 使用"泛型"来定义声明函数,这里的"泛型"可以用具体类型替换,比如int, double, long等,具体方法就是通过具体的类型如double作为参数, 传递给模板,编译器就会自动生成该类型函数

  • 另外的名字,①函数模板这种编程方法,也叫作通用编程,因为没有具体化, ②函数具体类型是由参数来表示的,这种模板特性也叫参数化类型

  • 如何创建函数模板??利用关键字template和typename,注意这里typename也是个关键字,区分我们之前的<typeName>这是我们自己为方便而写的一个概括而已

  • template <typename AnyType>//建立模板,类型是AnyType,如果你想用类class,即把typename关键字改成关键字class即可
    //函数定义声明如下
    void xjh(AnyType &a, AnyType &b)   
    {
        AnyType xjh1;     //定义声明变量xjh1是AnyType类型
        xjh1 = a;         //交换两个变量a和b的值,注意这里都是AnyType类型,可以是int double long long等
        a = b;
        b = xjh1;
    }
    int x =1000;
    int y =20000;
    xjh(x,y)
    //xjh()函数获得了两个int类型的值,所以编译器生成了函数的int版本

函数模板也不能总是乱用,就像函数重载,函数重载是在函数执行基本相同任务时,对象类型不同可以用函数重载,而函数模板则是在执行完全相同任务时,如果对象类型不同,则用函数模板

  • 函数重载的基本相同告诉我们,并非要执行完全相同的计算方法即算法,所以函数模板也可以进行重载,即模板重载

  • void xjh(AnyType &a, AnyType &b)   这里的函数模板,其实你也可以叫模板函数, 的特征标是AnyType &,AnyType &

  • 模板的实例化和具体化

    • 在代码中包含函数模板本身并不会生成函数定义,这个模板的工作只是用于生成函数定义的方案.就是说当编译器通过具体的类型来使用模板时,得到的是模板的实例, 这个模板的实例才是函数的定义声明

    • 联想变量的初始化,那么我们的模板能不能在创建模板时初始化一个类型呢?可以

二维数组

int data[3][4] = {
   
   {1,2,3,4}, {5,6,7,8}, {1,2,3,4}};

先看data,显然我们知道data是个数组名,也是个指针,常规的int data[];是说data是个int数组,data也是个指向int类型的指针

  •  
  • 这里的data是什么呢? data是 一个指针,这个指针指向的是个数组,这个数组是由4个int类型元素构成的,即int (*data)  [4] ;这样太晦涩难懂了, 也可以这样int data[][4];意义完全相同,这里都表明了data不是数组而是指针

  • 这里明白了,那么如何传递数组的长度呢?

    • 显然int data[][4]对行数是没有限制的,只是明确了列数要是4,即data指针指向的每个数组长度是4

  • 同样的,函数对C风格字符串没有办法传递和返回,我们直接用string更合适

  • 函数指针,函数指针这里交给指针笔记记录 

运算符的多态

函数有重载,即函数的多态,运算符也有,即运算符重载,运算符的多态.函数在的重载想让你用相同函数名完成基本差不多的操作.

运算符也有重载,实际上你已经学习过了,比如*即是乘号,也是地址运算符,根据运算符左右目来确定具体功能

我们知道函数重载可以自己定义,那运算符其实也可以

如何进行运算符重载?利用operator现有运算符(参数), 这里operator是个函数,不同的是,你需要在函数名后紧跟你需要重载的运算符,比如operator+()

//time.h
class time
{
    private:
        int hours;
        int munutes;
    public:
        time();
        time(int h, int m = 0);
        void AddMin(int m);
        void AddHr(int h);
        void Reset(int h=0, int m=0);
    
        time sum(const time & t) const;
        void Show() const;
// 加const表明,该函数只能是只读的,不能修改私有变量的值。加强安全性。
}
//endif 这里sum的参数运用了&运算符重载,即引用

//把stock::sum()函数利用运算符+的重载来实现
//time.h
class time
{
    private:
        int hours;
        int munutes;
    public:
        time();
        time(int h, int m = 0);
        void AddMin(int m);
        void AddHr(int h);
        void Reset(int h=0, int m=0);
    
        time operator+(const time & t) const;
        void Show() const;
}

当使用operator+成员函数时, 你也可以通过对象用.运算符来调用该方法函数,你也可以直接用+运算符来操作该

int main()
{
    total = coding.operator+(fixing);
    total1 = coding + fixing;
}

猜你喜欢

转载自blog.csdn.net/Mrsherlock_/article/details/109487436
今日推荐