面向对象程序设计
OOP(Object Oriented Programming)
int s1=1;
double s2=2.5;
char s3=‘g’ , s4=‘j’;
等价于
int s1(1);
double s2(2.5);
char s3(‘g’), s4(‘j’);
#include<string>
:
定义:
string类成员函数:
str.length()
和 str.size()
是string类对象的成员函数
strlen(str)
求字符数组的长度
三者均不包含’/0’!
string类操作符:
getline(cin, str);
换行符分割,空格当做符号
string类串 位置指针:
位置指针=迭代器 iterator
访问相应位置字符,可向前向后访问
不检查是否越界
常指针,仅读取,不能修改对象字符
string类 的串结尾不是’/0’
c串以NULL结尾
NULL、’/0’的数值都是0
'0’的数值是48!
https://blog.csdn.net/m0_37592397/article/details/79701992
string类串和c串:
wchar_t
为解决char256范围小的问题而提出
常量:文字常量,符号常量,常变量
常变量const 定义时一定要赋初值
逗号运算,
x=(a,b,c);
abc依次运算,c的值给x
内联函数inline:
空间换时间
调用函数变为顺序执行,跳转的时间没有了,程序执行更快
它不是被调用,而是编译器会扩展源代码,将内联函数代码复制到调用区域,这样是形式上调用,实际上顺序执行
内联函数:
不能有循环体
递归不能是内联
不能有swtich
形参默认值
从右向左依次默认定义,有右边的形参无默认值,则其左边就不能默认赋值
int max(int a, int b=1)√
int max(int a=1, int b=1)√
int max(int a=1, int b)×
int max(int a, int b=min(40, 5))√
声明时默认形参值,定义时可不默认形参值
调用函数时,其实参值权限>默认形参值
动态分配:
库函数:malloc、calloc、free
c++运算符:new、detele(效率高)
typedef struct LNode
{
int data;
struct LNode *next;
}*Link,LNode;
LNode *q,*p=L;
q = (LNode *)malloc(sizeof(LNode));
q = (LNode *)calloc(1, sizeof(LNode));
数组:
(int*)malloc(n * sizeof(int));
(int*)calloc(n, sizeof(int));
void *calloc(int n, int size);
内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。
malloc不初始化,分配到的空间中的数据是随机数据
calloc在动态分配完内存后,自动初始化该内存空间为零,用完后free(q)
对内存进行释放,不然内存申请过多会影响计算机的性能,以至于得重启电脑
持久化分配空间
int *p=new int(5);
等价与
int *p;
p=new int(5);
指针p指向初值为5的int对象
int *p=new int[5];
等价与
int *p;
p=new int[5];
指针p指向5个未初始化的int对象的首地址,不能定义时赋初值
即:p就是数组首地址
new失败会返回NULL
子函数内部定义的数组,子函数想返回这个数组的首地址
若普通定义,如 int a[5];它是局部变量,子函数调用完会释放局部变量!
这样定义可解决这个问题:
int *a = new int[5];
new是分配的持久内存空间
一般数组int a[这里不能是变量!];
int size=5;
int *a = new int[size]; size是变量,属于动态分配内存(堆区)
detele p; 撤销指针p,针对变量
detele[] p; 撤销数组空间,p是指针
new后记得detele!
Clock *c1= new Clock[5];
detele[] c1;
建立m*n动态二维数组
int **dm;
dm= new int*[m];
for(i=0; i<m; i++)
{
if( (dm[i]=new int[n])==NULL )
exit(0);
}
。。。
for(i=0; i<m; i++)
{
detele[] dm[i];
}
detele dm;
注意:detele的顺序和new的顺序相反
动态数组容量不够解决办法:
重新分配够用的空间b
原数组内容a复制到b
释放a的空间
对象:
是事物的抽象,
是属性、操作、方法的封装体
静态特征:可用数据描述的特征
动态特征:对象表现的行为或具有的功能
属性:描述静态特征的数据项
服务:描述动态特征的操作序列
对象间的通信:消息
消息的实现:函数调用、程序内部通信、各种事件的发生和响应
事件:系统事件、用户事件
系统事件:由系统激发,
如:时间每隔24小时,银行储户的存款日期增加一天
用户事件:由用户激发,
如:用户点击按钮,在文本框中显示特定的文本
对象有生命周期:
出生,生长,灭亡
创建(分配存储空间),
活动(自主运行,接收消息并处理),
删除(其存储空间设为无效并回收)
类:
是抽象,同一类对象的共同属性(静态特征)、行为(方法,动态特征)
即:有相同属性、方法的对象的抽象
类=属性+方法
属性:名字,类型,可修改型,可见性
方法:函数
类通过外部接口与外部发生关系
对象是类的具体实例
一个类的不同对象:
相同点:属性、方法
不同点:对象名、属性值
面向对象的基本特征:
封装,继承,多态
封装:
对象属性、方法的可见性(公有、私有)
继承:
子类 = 派生类
父类 = 基类 = 一般类
子类有父类的所有属性、方法
函数重载 函数覆盖=函数重写 多态
函数重载
函数名一样,参数不同
int max(int,int,int b=1)和
int max(int,int)会导致二义性
解决办法:增加或减少实参个数
同一范围中,函数名一样,但是函数的形式参数(参数的个数、类型、顺序)必须至少一个不同
即:同一个函数完成不同的功能,自然函数体细节实现不同
函数覆盖 = 函数重写:
父类与子类之间,
函数名、参数类型、返回值类型和父类对应函数必须严格一致(只有一种情况下返回值可以不一致:返回自己类的引用或者指针的时候)
也就是只有函数体不同
当子类对象调用该函数,会自动调用子类中的版本,而不是父类的版本
多态:
不同数据类型的实体提供统一的接口
人话:
父类有个虚函数,不同子类继承父类,要实现这个虚函数,不同之类实现该函数的细节和结果不同,这个不同就是多态(不同对象有不同的反应)
如:
父类是动物,有方法 [叫]
子类是猫和狗
猫的叫是[喵]
狗的叫是[汪]
多态,作用域不同
函数重载,作用域相同
封装
类和对象:
类:包含函数的结构体
是一个数据类型
类的数据成员不占空间,所以不能赋初值,可以用构造函数赋初值
类的数据成员不能是自己类型的变量
结尾;
类的成员函数:
放在类中定义就是内联的inline
也可以类中声明,类外定义
void clock::max(int a, int b){}
private的数据成员,在类外无法访问
可通过public的成员函数来访问,并赋值
构造函数,对象的初始化赋值
析构函数,清理工作
结构体对象初始化
结构体名 具体对象名(值1, 值2, 。。。。)
类对象初始化,有构造函数的话,就可以像结构体对象那样初始化
c++默认帮你生成一个构造函数,即使你没写,这个默认构造函数是空的!
自定义后,以自定义为准,默认构造函数就不存在了
法一:类名 对象名(值1,。。)
Clock my1(1, 2);
(结构体可直接这样初始化)
法二:类名 对象名= 类名(值1,。。。)
Clock my1=Clock(1, 2);
构造函数:
必须是public
不写返回值类型,写void也不行
必须与类名一样
可重载
建立对象时系统自动调用
动态申请内存
析构函数:
对象消失时,释放析构函数申请的内存
有默认的析构,但它啥事也不做!
一般要自定义
形式:
~类名();
类名=构造函数名
必须是public
可以是虚函数
不写返回值类型,写void也不行
无参
只能有一个析构函数,不能重载
析构函数的清理语句完成对象的消失
~String()
{
if(Str!=NULL)
{
detele[] Str;
Str=NULL;
}
}
构造函数用的时候是对象的初始化建立,属于局部变量,放在栈中
一个类的多个对象创建,构造函数的调用顺序=创建顺序
析构函数的调用是创建顺序的相反
拷贝构造函数 = 克隆
以现存对象为新对象的初值
c++默认帮你构造一个拷贝构造函数
也可以自定义:
Clock(Clock &c)
{
xx=c.xx;
。。。
}
触发拷贝构造函数的条件:
1 一个已存在的类对象初始化另一个新建的类对象
Clock c1(1,2,3);
Clock c2(c1);
2 函数形参是类对象,已存在的对象作为实参传给形参
3 函数返回值是类对象
Clock(1,2,3);
不是已存在对象(变量),它是常量!
对象指针和对象引用不触发构造函数,因为他们没有建立新的对象
浅拷贝 深拷贝:
默认拷贝是浅拷贝,即已存在对象和新对象的拷贝是成员值赋值,它没有给新对象分配内存资源
若是指针,两对象的指针指向同一个内存地址,两对象的析构函数调用时,前一个析构已经释放这个内存了,后一个析构就会出现问题!
深拷贝就是解决这个问题的:
String(String &s)
{
len=s.len;
if(len!=0)
{
Str=new char[len+1];
Strcpy(Str, s.Str);
}
}
c++默认帮你生成一个拷贝构造函数,即使你没写,自定义后,以自定义为准,默认拷贝构造函数就不存在了
对象指针:
Clock c1(8,0,0);
Clock *p1=&c1, p2;
p2=&c2;
p1->。。。
(*p1).。。。
函数参数用对象指针 而不是对象的好处:
1 对形参的改变影响着实参
2 传递的是地址,缩小时空开销效率更高
对象引用:
类似引用
对象是变量,类是数据类型
数据类型不存在引用,故 类引用不存在
Clock &c2=c1;
对象引用
同类型
定义必须初始化(函数参数 函数返回值的引用不需要初始化)
是别名,不新分配内存
this指针:
类中有private变量a
只能本类的成员函数给a赋值,若形参也叫a
用this->a=a;
(*this).a=a;
this指针 是指针常量
this不是对象的别名,而是指向 调用对象的指针
组合类:
构造函数形式:
类名(形参表): 成员对象1(子形参表1),成员对象2(子形参表2),。。{}
Clock(int a,int b,int c): newClock1(a,b), newClock2(b,c){}
调用形式:
类名 对象名(实参表);
Clock c(1,2,3);
静态成员static:
是类的属性,类的不同对象,其静态成员一样
其被访问权限和一般成员一样
静态:存储空间独立分配,
静态:数据成员、函数成员
静态数据成员
定义类后,对象创建前就存在了
类内声明,内外定义
类内声明:static int a;
内外定义 int A::a=1; (无static!)
静态函数成员
定义类后,对象创建前就存在了
类内声明,内外定义
类内声明:static int max(。。);
内外定义 int max(。。。) {} (无static!)
或直接类中声明定义
调用:
类名::静态函数名(。。。);
常对象 常成员:
int const 对象名;
const int 对象名;
等价的
常对象仅被类的常成员函数访问!它不能被修改
常对象仅可访问它的常成员函数,这是常对象唯一的对外接口方式
一般对象可以访问常成员函数
常对象定义就要初始化,用的是构造函数初始化
常成员函数:
声明
int max(。。)const;
实现:
int max(。。)const{}
const不能丢!
常成员函数不能更新对象的数据成员(可以是常对象的,也可不是;数据成员可const,也可非const),
常成员函数不能调用一般成员函数
常成员函数重载
声明:
原函数:
int max(。。){}
常成员函数重载:
int max(。。) const{}
一般对象名.max
常对象名.max
不加const,两个函数同名 但两者没有关系
这就是重载了
常数据成员仅通过构造函数初始化列表进定义:
int const a;
const int a;
初始化:
A(int i): a(i){}
友元函数
对于类的private成员(属性 数据 、 方法 函数)
只有类的成员函数可以访问
若private成员很多
在类外,可能有很多种访问private成员的组合可能
对每种可能都写一个成员函数不现实
把private改为public,则失去c++封装隐蔽的优点(这时候优点变缺点)
用友元函数!
友元:
不属于类的成元
可以访问类的所有成员
可以是另一个类的成员,也可不是,
可以是不属于任何类的一般函数
可以是一个整类
友元函数定义:
friend int max(。。){}
可在类中声明在内外定义,内外定义不可指定该友元函数是属于哪个类(因为友元不属于任何类)
声明加friend,内外定义不用加friend,即不能对象名.友元函数名(实参)
友元类定义:
friend class A{}
两个友元类相互引用:
class B
class A{friend class B。。。}
class B{friend class A。。。}
继承、派生:
子类 = 派生类
父类 = 基类 = 一般类
子类有父类的所有属性、方法
类层次:父类、子类的这种关系
继承:子类有父类的所有属性和方法(变量和函数)
单继承:子类只有1个父类
多继承:子类有2个以上的父类
多层继承:子类b继承于类a,子类b又是类c的父类(单继承、多继承都可以有多层继承)
继承方式:
public 公有
protect 保护
private 私有(默认继承方式)
private成员被子类继承了,子类也无法访问该成员
对于protect成员,子类的成员函数可访问,子类对象不能访问
class b: public a, private c{}
子类继承后可以有的操作:
1 继承数据成员和函数成员
2 改造父类成员,对函数成员的改造叫覆盖
3 添加新成员
函数重载:
同一范围中,函数名一样,但是函数的形式参数(参数的个数、类型、顺序)必须至少一个不同
即:同一个函数完成不同的功能,自然函数体细节实现不同
函数覆盖 = 函数重写:
父类与子类之间,
函数名、参数类型、返回值类型和父类对应函数必须严格一致(只有一种情况下返回值可以不一致:返回自己类的引用或者指针的时候)
也就是只有函数体不同
当子类对象调用该函数,会自动调用子类中的版本,而不是父类的版本
继承不可以循环
即父类和子类不能相互继承
继承与组合类
继承:一般与特殊
组合:整体和部分
子类的构造函数与析构函数
多继承构造函数形式:
class b(参数总表):
父类a1(参数表1), 父类a2(参数表2)。。。,
成员对象1(参数1),。。。
{}
父类的构造函数有参数,子类构造函数就要提供对应的初值,反之可以不提供
子类构造函数就要提供对应的父类初值的原因:子类对象创建,出发触发父类构造函数,这得有初值呀,不然报错
父类构造函数触发调用的顺序是:子类继承时的从左往右的顺序,与之类构造函数初始化列表的顺序无关的顺序
class b: public a, private c{}
构造函数触发顺序:先a后c
父类析构函数触发调用的顺序是:调用顺序的相反(就栈进栈出的顺序呗)
二义性问题:
父类a1、a2有同名成员函数max(变量也一样)
子类b继承了a1、a2
b类内调用max(),程序如何知道这是谁的max()?
解决办法:
成员函数名限定:a1::max();
成员函数重定义(或许是覆盖,或许函数重载)
这个办法依然有问题:
例子:
a
b c
d
class a有数据i
class b继承a,i=5
class c继承a,i=9
clasa d继承b、c
d对象调用i,i是几?
不同途径继承同名数据成员,造成终子类的数据不一致问题
解决办法:虚基类
虚基类:
在继承时用到
形式:
class b: virtual public a, public c{}
class d: virtual public a, public e{}
a是b和d的虚基类,c不是b的虚基类,e不是d的虚基类
a是虚基类,它的成员在被继承时,在系统中只有一份拷贝
虚基类的构造函数有参数,其所有子类,子类的子类 的构造函数都要给其参数:
虚基类a构造函数
a(int b){}
子类a1构造函数:
a1(int i):a(i){}
子类a2构造函数:
a2(int j):a(j){}
最远派生类构造函数a12
a12(int i, int j, int k):a(i),a1(j),a2(k){}
虚基类构造函数触发调用:
只有最远派生类创建对象时,只调用虚基类的构造函数,中间的类不调用其构造函数,保证虚基类的成员只初始化一次
虚基类调用构造函数的顺序:
(1)虚基类的构造函数在非虚基类之前调用;
(2)同一层次中包含多个虚基类,其构造函数按说明的次序调用;
(3)若虚基类由非虚基类派生而来,仍先调用基类构造函数,再调用派生类的构造函数。
类型兼容:
子类对象可作为父类对象使用
1
子类对象可赋值给父类对象
父类对象名=子类对象名
拷贝构造函数
2
子类对象可初始化父类引用
父类 &父类对象名=子类对象名;
3
子类对象地址可初始化指向父类的指针
父类 *父类对象名=&子类对象名;
多态:
函数重载 函数覆盖 多态
函数重载
函数名一样,参数不同
int max(int,int,int b=1)
和int max(int,int)
int max(1,1)
这种调动会导致二义性
解决办法:增加或减少实参个数
同一范围中,函数名一样,但是函数的形式参数(参数的个数、类型、顺序)必须至少一个不同
即:同一个函数完成不同的功能,自然函数体细节实现不同
函数覆盖 = 函数重写:
父类与子类之间,
函数名、参数类型、返回值类型和父类对应函数必须严格一致(只有一种情况下返回值可以不一致:返回自己类的引用或者指针的时候)
也就是只有函数体不同
当子类对象调用该函数,会自动调用子类中的版本,而不是父类的版本
多态:
同样的信息被不同类型对象接受,有不同的行为(函数不同)
人话:
父类有个虚函数,不同子类继承父类,要实现这个虚函数,不同之类实现该函数的细节和结果不同,这个不同就是多态(不同对象有不同的反应)
如:
父类是动物,有方法 [叫]
子类是猫和狗
猫的叫是[喵]
狗的叫是[汪]
多态,作用域不同
函数重载,作用域相同
多态:
编译多态,运行多态
编译多态:编译时,确定同名操作的具体对象
运行多态:运行时,动态确定操作针对的具体对象
联编:确定具体对象的过程
静态联编,动态联编
静态联编:编译、连接时进行(函数重载,函数模板)
动态联编:运行时
运算符重载
只有5个运算符不能被重载:
. 选择运算符
.* 指针运算符
:: 作用域分辨符
?: 三目运算符
sizeof 数据大小运算符
一元运算符: T obj → operator T(obj)
!a → operator !(a)
二元运算符:obj1 T obj2 → operator T(obj1,obj2)
1 + 2 → operator +(1,2)
前置++
、前置--
遵循一元运算符规则
后置++
、后置--
:obj T → operator T(obj,0)
a ++ → operator ++(a,0)
运算符重载规则:
只针对已存在的运算符
不改变原有语义、优先级、参数个数
改变的参数(形参、实参)的数据类型
operator + (8,9);
operator + (8.0,9.0);
其函数原型:
int operator + (int ,int);
double operator + (double ,double);
运算符重载为为类的友元函数
这样可访问类的所有数据成员!
friend int operator +(形参表)
{。。。}
运算符重载为类的成员函数
int 类名::operator +(形参表) 此时声明在类内,定义在类外
{。。。}
此时形参表会少写一个参数
如二目运算,有两个参数,即两个对象a1、a2
因为此时是类的成员函数,对象a1可调用运算符
运算法的参数写对象a2
形式:
类名
{
。。。
类名 operator +(类名 对象名);
Clock operator +(Clock B);
}
Clock Clock::operator +(Clock B)
{
return。。。
}
main中调用:
{
Clock A(。。。),B(。。。),C;
C=A+B;
。。。
}
1:
Clock D(。。。);
return D;
2:
return Clock(。。。);
1效率低
2是常量,效率高
运算符重载为类成员函数:
单目运算(一元运算)
=、()、[]、->
涉及修改对象的状态
类型转换函数(一定不能是友元函数)
运算符重载为友元函数:
二元运算,但=、()、[]、->
这些二元运算不行((int)i
对应显式类型转换)
隐式类型转换
运算符有可交换性
A+B
左右是类对象
显式类型转换:
float i= 5.5;
int j = (int)i;
隐式类型转换:
int i= 5.5;
float j = i;
运算符=
重载为成员函数
=
不重载就是浅拷贝
重载的目的在于深拷贝
运算符[]
重载为成员函数:
class Clock
{
private:
char *Str;
int len;
public:
Clock & operator [] (int n)
{
return *(Str+n);
}
}
运算符->
重载为成员函数:
可防止指针指向NULL!防止野指针!
class myClock
{
private:
Clock *p; 注意本类是类Clock的子类
public:
Clock * operator ->
{
static Clock a(实参);
if(p==NULL)
return &a;
return p;
}
}
()运算符重载为成员函数:
()成为函数调用运算符
class Add
{
double operator()(double a, double b)
{retern a+b;}
}
Add的实例对象就是函数对象
虚函数
多态:
编译多态,运行多态
编译多态:编译时,确定同名操作的具体对象
运行多态:运行时,动态确定操作针对的具体对象
联编:确定具体对象的过程
静态联编,动态联编
静态联编:编译、连接时进行(函数重载,函数模板)
动态联编:运行时
静态联编:
父类 Clock
子类 Clock_asd
Clock *p,q=Clock(。。。);
Clock_asd r=Clock_asd(。。。),&k=q;
p=&r; 静态联编
&k=q; 静态联编
p->函数名;调用的是父类的函数,但实际上我们想用子类的函数
用虚函数解决这个问题!
这对应动态联编
virtual 函数类型 函数名(形参表){}
静态成员函数、友元函数:不属于某个对象
内联函数:不是动态的,是编译时确定的
构造函数:初始化数据成员的,是确定的,不是动态确定的
虚函数不能是:静态成员函数、友元函数、内联函数、构造函数
虚函数可在类中声明、定义,在继承的子类中可以重定义(也可以不重定义)
虚函数可以是析构函数
虚函数父类有多次继承的子类,子类用该函数时,用的是这个父类的该函数!
虚函数不能被重载覆盖,子类对象引用子类虚函数会出现错误!
虚函数使用规则:
类型兼容
子类可以重定义
如何被访问:成员函数、指针、引用
虚析构函数
virtual ~类名();
父类的析构函数是虚函数,其下面的子类都是虚析构函数
好处:
父类类型指针可以的调用恰当的析构函数 完成不同类型对象的清理
使用情景:
父类类型指针,子类的对象清理
抽象类
抽象类:含有纯虚函数的类
纯虚函数:
virtual 函数类型 函数名(形参表)=0
空虚函数
virtual 函数类型 函数名(形参表){ 空的!}
1 抽象类不能实例化(不能有自己的对象),只能被其他类继承
2 子类继承抽象类,若不给出纯虚函数的定义,则自己也是抽象类
3 抽象类不能用作 参数类型、函数返回值、强制类型转换
函数模板:
调用:显式调用、隐式调用
显式调用:
函数名<数据类型表>(实参表);
max<double>(40, 68)
隐式调用:函数调用式 max(10,45)
max(10,45) 对应 int max(int, int)
max(10,45.0) 对应 int max(int, double)
max(10.0 , 45) 对应 double max(double,int)
系统函数:
复制的两个函数:
memcpy(目标地址,源地址,字节数n)
1、strcpy只能复制字符串,memcpy可复制任意内容,例字符数组、整型、结构体、类
2、复制的方法不同。
strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度
sizeof(数组名)
=sizeof(n*a[0])=sizeof(n*a[1])
=整个数组的字节数
sizeof(指针p)=4(32bit)或8(64bit)
而不是p指向的数据的字节数
类模板
template<模板参数表>
class 类名
{
。。。
};
类模板的成员函数在类外定义形式:
template<模板参数表>
类型 类名<模板参数表>::函数名(参数表)
{函数体}
类模板实例化形式:
模板名<模板参数表> 对象名1,对象名2,。。。
具体:
class String {
public:
char Str[20];
}
main()
{
Student<String,float,100> S1;
。。。
}
默认模板参数:
Student<char*> S1;
栈类模板
链表模板
STL:
standard template library 标准模板库
不让程序员重复发明轮子
STL编程:
逻辑上,泛型化程序设计
代码重用:现有模板程序代码开发程序
实现上,类型参数化,基于模板的标准库类,容器类
STL:
容器 container
迭代器 iterator
适配器 adaptor
算法 algorithm
函数对象 function object
容器(均是类模板):
顺序容器
关联容器
容器(均是类模板):
vector 向量
list 列表
deque 队列
set/multiset 集合
map/multimap 映射
顺序容器:
vector 向量,动态数组,查快 插删慢
list 双向链表,查慢 插删快
deque 双端队列,包含vector、list两者的优点特性,可看做两者的融合
关联容器:
set/multiset 集合
map/multimap 映射
顺序容器:
vector 有成员方法
list 无成语方法
deque 有成员方法
异常: