类
类声明
//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;
}