目录
类和对象
1.面向对象和面向过程的初步认识
c语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
c++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
类的引入
在c语言中,结构体只能定义变量,在c++中,结构体不仅可以定义变量,还可以定义函数
类的定义
class ClassName
{
//类体:由成员函数和成员变量组成
};
class为定义类的关键字,className为类的名字,{}中为类的主体,注意类定义结束时后面分号。
类中的元素称为类的成员:类中的数据据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
类的两种定义方式:
1.声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理
2.声明放在.h文件中,类的定义放在.cpp文件中
类的访问限定符及封装
访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
访问限定符说明:
1.public修饰的成员在类外可以进行访问
2.protected和private修饰的成员在类外不能直接被被访问
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
4.class的默认访问权限为private,struct为public(因为struct要兼容C)
问题:在c++中struct和class的区别是什么?
:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式private。
封装
面向对象的三大特性:封装,继承,多态.
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
#include<iostream>
using namespace std;
class Date
{
public:
void print();
private:
int _year;
int _month;
int _day;
};
void Date::print()
{
cout<<_year<<"-"<<_month<<"-"<<endl;
}
类的实例化
用类类型创建对象的过程,称为类的实例化
1.类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来储存它
2.一个类可以实例化出多个对象,实例化的对象 占用实际的物理空间,储存类成员变量
类对象模型
类对象的大小
类对象的大小计算和C语言中结构体的大小计算类似,同样遵循内存对齐的规则,但类比结构体多了成员函数。而所有类对象都会调用同一份成员函数,如果每个对象都保存一份成员函数,则会造成很大的空间浪费。** 因此对象的内存中只保存成员变量,成员函数存放在公共的代码段中。**
class A1{
public:
void f1(){
}
private:
int _a;
};
class A2{
public:
void f1(){
}
private:
int _a;
int _b;
};
class A3
{};
int main()
{
cout<<sizeof(A1)<<endl;
cout<<sizeof(A2)<<endl;
cout<<sizeof(A3)<<endl;
};
空类的大小为1,,编译器给了空类一个字节来唯一标识这个类。
this指针
此时我们有个疑问:
在C++里面 成员函数是怎么知道哪个对象的呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
对于成员函数的初始化的调用d1.Init(2021, 11,13)===d1.Init(&d1,2021,11,13)
类里面的成员函数也会被编译处理成
void Init(Date*this,int year,int month,int day)
{
this->_year=year;
this->_month=month;
this->_day=day;
}
特性:
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
类的6个默认成员函数
任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特征:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数(不用参数就可以调用的构造函数)。
析构函数
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
特性:
1.析构函数名是在类名前加上字符~。
2.无参数无返回值
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
拷贝构造函数
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3.若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
浅拷贝对于数值是没有问题的,但对于涉及地址就有可能出现问题
此处就是多次释放同一处空间
那么我们假设判断一下这个空间是否为空从而进行是否要free呢??—结果一样还是不行的
赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类
型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数原型:返回值类型 operator操作符(参数列表)
注意
- 运算符重载和函数重载都用了“重载”这个词,但是两者没有关联,函数重载是支持定义同名函数,运算符重载是为了让自定义类型可以像内置类型一样去使用运算符。
- 不能通过重载来创建新的运算符:比如operator@,C语言和C++中没有@这个运算符,所以不能重载@
- 重载操作符必须有一个自定义类型或者枚举类型的操作数,也就是参数必须有一个自定义类型或者枚举类型
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的,因为操作符有一个默认的形参this,限定为第一个形参
- .* 、 :: 、 sizeof 、 ?: 、.注意以上5个运算符不能重载。
举个例子
bool operator=(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
const成员
const对象不可以调用非const成员函数,因为是权限的放大。
非const对象可以调用const成员函数,因为这是权限的缩小。
const成员函数内不可以调用其它的非const成员函数,因为权限放大。
非const成员函数内可以调用其它的const成员函数,因为权限缩小。
const里面设计权限的缩小与放大
大的权限可以推导出小的权限
小的权限推导不出大的权限
const 对象只有读的权限不具备写的权限,非const的对象具有读写的权限
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
Date* operator&()
{
return this;
}
//这两个函数构成重载
const Date* operator&()const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
时间类的整体实现
class Date
{
public:
int getMonthDay(const int year, const int month);
Date(int year, int month, int day);
void operator=(const Date& d);
bool operator==(const Date& d) const;
bool operator!=(const Date& d)const;
bool operator>(const Date& d) const;
bool operator<(const Date& d) const;
Date operator-=(const int day);
Date operator+=(const int day);
Date operator-(const int day)const;
Date operator+(const int day);
Date* operator&();
//这两个函数构成重载
const Date* operator&()const;
Date operator++();
Date operator++(int);
Date operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
inline int Date::getMonthDay(const int year, const int month)
{
static int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && (month == 2))
return 29;
return day[month];
}
Date::Date(int year = 0, int month = 1, int day = 1)
{
if (year >= 0 && month > 0 && month < 13 && day>0 && day <= getMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
cout << "???你输入啥" << endl;
}
bool Date::operator==(const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator>(const Date& d) const
{
if (_year > d._year)
return true;
if (_year == d._year && _month > d._month)
return true;
if (_year == d._year && _month == d._month && _day > d._day)
return true;
return false;
}
bool Date::operator<(const Date& d) const
{
if (_year < d._year)
return true;
if (_year == d._year && _month < d._month)
return true;
if (_year == d._year && _month == d._month && _day < d._day)
return true;
return false;
}
Date Date::operator-=(const int day)
{
if (day < 0)
{
return (*this) - (-day);
}
else
{
_day -= day;
while (_day <= 0)
{
_day += getMonthDay(_year, _month);
_month--;
if (_month <1)
{
_year--;
_month = 12;
}
}
}
return *this;
}
Date Date::operator-(const int day) const
{
Date tmp(*this);
tmp -= day;
return tmp;
}
Date Date::operator+=(const int day)
{
if (_day <= 0)
{
return (*this) -= (-day);
}
else
{
_day += day;
while (_day > getMonthDay(_year, _month))
{
_day -= getMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
}
}
Date Date::operator+(const int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
Date* Date::operator&()
{
return this;
}
const Date* Date::operator&()const
{
return this;
}
bool Date::operator!=(const Date& d)const
{
return !((*this) == d);
}
void Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date Date::operator++()
{
return (*this) += 1;
}
Date Date::operator++(int)
{
Date tmp(*this);
tmp += 1;
return tmp;
}
Date Date::operator--()
{
return (*this) -= 1;
}
Date Date::operator--(int)
{
Date tmp(*this);
tmp -= 1;
return tmp;
}
友元
友元分为:友元函数和友元类
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。 我们拿上时间类来进行说明。
class Date
{
friend void print(Date& d);
public:
int getMonthDay(const int year, const int month);
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void print(Date& d)
{
cout << d._year << " " << d._month << endl;
}
void print2(Date& d)
{
cout << d._year << endl;
}
int main()
{
Date d;
return 0;
}
在上图可以看出友元的一些作用
一些类的输入输出,由于自己类没有实现cin和cout,我们要自己进行实现
class Date
{
friend ostream& operator<<(ostream& out, Date& d);
friend istream operator>>(istream& in, Date& d);
public:
int getMonthDay(const int year, const int month);
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, Date& d)
{
out<<d._year<<"-"<<d._month<<"-"<<d._day;
return out;
}
istrean& operator>>(istream& in,Date& d)
{
in>>d._year>>d._month>>d._day;
return in;
}
int main()
{
Date d;
cin>>d;
return 0;
}
这样写 如果不适用友元函数的话,重载operator>>(ostream& out) 前面会有一个隐藏的this指针,要调用的话,则会变成 d.>>(cin); 或者operator>>(d,cin);
友元函数可以直接访问类的私有和保护成员,但不是类的成员函数
友元函数不能const修饰
友元函数可以在类里面的任何地方声明,不受类的访问限定符限制
一个函数可以拥有多个友元函数
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
友元关系不具备传递性.如果B是A的友元,C是B的友元,则不能说明C时A的友元。
#include<iostream>
using namespace std;
class B;//声明类B 原因要使用友元,系统查找的是往上进行查找
class A {
friend class B;
public:
private:
int _a;
};
class B {
public:
void print()
{
cout <<"call A _a" << endl; //友元类的使用
cout << _a._a << endl;
}
private:
int _b;
A _a;
};
int main()
{
B b;
b.print();
return 0;
}
内部类
概念
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
简单点:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
本质: 内部类就是一个友元类,若想外部类可以使用内部类的成员,可以借助友元来进行突破.
#include<iostream>
using namespace std;
class A
{
private:
int _a;
public:
// 内部类
// 1、内部类B和在全局定义是基本一样的,只是他的受外部类A类域限制,定义在A的类域中
// 2、内部类B天生就是外部类A的友元,也就是B中可以访问A的私有,A不能访问B的私有
class B
{
public:
void foo(const A& a)
{
cout << "内部类调用外部类ok" << endl;
cout << a._a << endl;
}
private:
int _b;
};
};
int main()
{
A aa;
A::B bb;//类B在类A的内部
bb.foo(aa);
return 0;
}
外部类调用内部类的成员变量 使用在内部类里面声明外部类为友元类
#include<iostream>
using namespace std;
class A
{
private:
int _a;
public:
// 内部类
// 1、内部类B和在全局定义是基本一样的,只是他的受外部类A类域限制,定义在A的类域中
// 2、内部类B天生就是外部类A的友元,也就是B中可以访问A的私有,A不能访问B的私有
class B
{
friend class A;
public:
void foo(const A& a)
{
cout << "内部类调用外部类ok" << endl;
cout << a._a << endl;
}
private:
int _b;
};
void foo(B& b)
{
cout<< "外部类调用内部类ok" << endl;
cout << b._b << endl;
}
};
int main()
{
A aa;
A::B bb;//类B在类A的内部
//bb.foo(aa);
aa.foo(bb);
return 0;
}
特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
构造函数的初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
#include<iostream>
using namespace std;
class A {
A(int a=0,int b=0,int c=0)
:_a(a)//从冒号凯斯
,_b(b)//往后都是逗号
,_c(c)
{}
private:
int _a;
int _b;
int _c;
};
int main()
{
return 0;
}
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
#include<iostream>
using namespace std;
class A {
public:A(int a = 0, int b = 0, int c = 0)
:_b(b)
, _a(_b)
, _c(c)
{}
void print()
{
cout << "-a=" << _a << " " << "_b=" << _b << "_c=" << _c << endl;
}
private:
int _a;
int _b;
int _c;
};
int main()
{
A a(10, 11, 12);
a.print();
return 0;
}
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{}
void print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A a = 1024;
a.print();
return 0;
}
原理:上面没有加explict ,已经发生隐式转换 ,数值1024发生隐式转换产生一个匿名对象,然后将匿名对象的值与a发生拷贝,故打印出来的是1024,
发生隐式转换和拷贝,编译器会优化
下面是加了explicit的代码及效果
#include<iostream>
using namespace std;
class A {
private:
int _a;
public:
explicit A(int a = 0)
:_a(a)
{
cout << "check()" << endl;
}
A(A& a)
{
_a = a._a;
}
void print()
{
cout << _a << endl;
}
};
int main()
{
A a=1024;
return 0;
}
staric 成员
1.明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化
2. 静态成员为所有类共享,
3.静态成员函数没有隐藏的this指针,不能访问任何非静态成员
4. 类静态成员即可用类名::静态成员或者对象.静态成员来访问