首先来理解什么是面向对象编程。
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
在C++中的结构体内不仅可以定义变量,也可以定义函数。在C++中常用class来代替struct
struct Student
{
void Set(const char* name, const char* gender, int age)
{
}
void Print()
{
}
char _name[20];
char _gender[3];
int _age;
};
class className
{
// 类体:由成员函数和成员变量组成
};
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,类定义结束时一定要加后面分号。
类中的元素称为类的成员:
class className
{
public:
//公共的行为或属性
private:
//私有的行为或属性
};
class className
{
public:
void show();
//公共的行为或属性
private:
//私有的行为或属性
};
在.cpp中
void className::show()
{
cout<<_name<<endl;
}
一般情况会采用第二种方式。
访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
C++的访问限定符三种:public(公有) , protected(保护), private(私有)。
面向对象的有三大特性:封装、继承、多态。在类和对象阶段,只研究类的封装特性。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理。
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
class className
{
public:
void show();
//公共的行为或属性
private:
//私有的行为或属性
};
void className::show()
{
cout<<_name<<endl;
}
用类类型创建对象的过程,称为类的实例化。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图。与C语言中的结构体相同。
类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
我们通过对下面的不同对象分别获取大小进行分析:
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
sizeof(A1) = 4; sizeof(A2) = 1; sizeof(A3) = 1;
this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。 一般存在栈中。
若类中任何成员都没有就称之为空类。但它不会真的什么都没有,编译器会自动生成6个默认的成员函数:
构造函数:完成初始化工作;
析构函数:完成清理函数;
拷贝构造:使用同一个类的对象初始化创建对象;
赋值重载:主要是把一个对象赋值给另一个对象;
取地址重载:有两个函数,主要是对普通对象和const对象取地址;
class A
{
public:
A();//构造函数
A(const A& a);//拷贝构造函数
~A();//析构函数
A& operator=(const A& a);//赋值运算符重载
A* operator &();//取地址运算符重载
const A* operator &() const;//const修饰的取地址运算符重载
};
下面我将对这六个默认的成员函数进行介绍。
构造函数是一个特殊的成员函数,在对象的生命周期内只调用一次。 构造函数名与类名相同。构造函数没有返回值。在对象进行实例化的时候编译器自动调用对应的构造函数。构造函数可以重载。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
以Date类为例
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1) //构造函数
{
_year = year;
_month = month;
_day = day;
cout << "1" << endl;
}
private:
int _year; //对象的命名风格
int _month;
int _day;
};
class Date
{
public:
// 1.无参构造函数
Date ()
{}
// 2.带参构造函数
Date(int year , int month, int day) //构造函数
{
_year = year;
_month = month;
_day = day;
cout << "1" << endl;
}
private:
int _year; //对象的命名风格
int _month;
int _day;
};
void Test()
{
Date d1; // 调用无参构造函数
Date d2 (2021, 5, 30); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
Date d3();
}
析构函数:与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数名也应与类名相同,只是在函数名前面加一个位取反符~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。
typedef int DataType;
class SeqList
{
public :
SeqList (int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList() //析构函数
{
if (_pData)
{
free(_pData ); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private :
int* _pData ;
size_t _size;
size_t _capacity;
};
会自定类型成员调用它的析构函数。
1.概念
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2.特性
拷贝构造函数也是特殊的成员函数。
其特征如下:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1) //构造函数
{
_year = year;
_month = month;
_day = day;
cout << "1" << endl;
}
Date(Date& d) //拷贝构造函数,不可以Date(Date d)会造成无限递归
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "2" << endl;
}
~Date() //析构函数
{
}
private:
int _year; //对象的命名风格
int _month;
int _day;
};
1.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
需要注意的:
2.赋值运算符重载
赋值运算符主要有四点:
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1); //1.构造函数,全缺省。
void Print(); //打印
//析构,拷贝构造,赋值重载,可以不写,默认生成
//stack才写
//运算符重载,日期加减
Date& operator+=(int day); //+=之后,day这个实体还是存在的,所以可以返回引用
Date operator+(int day);
Date& operator-=(int day);
Date operator-(int day);
//()为空是前置++,增加(int)是为了构成重载,后置++;
Date& operator++(); //++d 前置可以引用,因为是先加自己身上再返回
Date operator++(int); //d++ 用int占位,不需要给实参
Date& operator--();
Date operator--(int);
// 运算符重载
bool operator>(const Date& d)const;
bool operator<(const Date& d)const;
bool operator==(const Date& d)const;
bool operator<=(const Date& d)const;
bool operator>=(const Date& d)const;
bool operator!=(const Date& d)const;
// 日期-日期 返回天数
int operator-(const Date& d);
private: //私有
int _year;
int _month;
int _day;
};
inline int GetDay(int year, int month) //返回当月最大日期,频繁调用所以内联
{
static int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; //12个月的日期,只创建一次,放在静态区
int day = days[month - 1];
if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) ) //判断闰年
{
day = 29;
}
return day;
}
Date::Date(int year, int month, int day) //指定类域,缺省函数只能在一个地方出现
{
if (year >= 0
&& month <= 12 && month >= 1
&& day > 0 && day <= GetDay(year, month)) //判断日期的合法性
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << " no " << endl;
assert(false);
}
}
void Date::Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
Date& Date::operator+=(int day) //
{
if (day < 0) //day<0
{
_day -= -day;
}
else
{
_day += day;
while (_day > GetDay(_year, _month)) //判断day是否合法,不合法就月份+1,月份超出就置1,年++;
{
cout << GetDay(_year, _month) << _month<<endl;
_day -= GetDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
}
return *this;
}
Date Date::operator+(int day) //需要创建一个新的对象,返回这个对象
{
Date ret(*this); //使用默认拷贝构造函数(浅拷贝)
ret += day; //直接进行复用,直接操作对象,不要操作成员。
return ret;
}
Date& Date::operator-=(int day)
{
if (day < 0) //减负数
{
_day += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
_month--; //先-月,因为要往上一个月算,
if (_month < 0) //逻辑参考+=
{
_month = 12;
_year--;
}
_day += GetDay(_year, _month);
}
}
return *this;
}
Date Date::operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
//Date:: 声明域
bool Date::operator>(const Date& d)const
{
if (_year < d._year)
{
return false;
}
else if (_year > d._year)
{
return true;
}
else
{
if (_month < d._month)
{
return false;
}
else if (_month > d._month)
{
return true;
}
else
{
if (_day > d._day)
{
return true;
}
else
{
return false;
}
}
}
}
bool Date::operator<(const Date& d)const
{
if (_year > d._year)
{
return false;
}
else if (_year < d._year)
{
return true;
}
else
{
if (_month > d._month)
{
return false;
}
else if (_month < d._month)
{
return true;
}
else
{
if (_day < d._day)
{
return true;
}
else
{
return false;
}
}
}
}
bool Date::operator==(const Date& d)const
{
if (*this > d || *this < d)
return false;
else
return true;
}
bool Date::operator<=(const Date& d)const
{
if (*this > d)
return false;
else
return true;
}
bool Date::operator>=(const Date& d)const
{
if (*this < d)
return false;
else
return true;
}
bool Date::operator!=(const Date& d)const
{
if (*this == d)
return false;
else
return true;
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date ret(*this);
*this += 1; //这里不能对ret操作,因为要做返回值,this实际上要加,但为了返回一个加之前,所以返回一个ret
return ret;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
int Date::operator-(const Date& d) //设置两端,然后遍历整个区间,++i就可以。
{
int flag = 1; //判断日期正负,默认为正
Date max = *this; //默认d小,如果不采取这种方式,下面while会存在编译错误
Date min = d;
if (*this < d)
{
max = d; //拷贝构造的另一种表达法,不能重复构建对象,重复定义根据就近原则会以上面的为最终结果
min = *this;
flag = -1;
}
int n = 0;
while (max > min)
{
++n;
++(min);
}
return n * flag; //大小*正负
}
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。并且非常少使用。
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public :
void Display ()
{
cout<<"Display ()" <<endl;
cout<<"year:" <<_year<< endl;
cout<<"month:" <<_month<< endl;
cout<<"day:" <<_day<< endl<<endl ;
}
void Display () const
{
cout<<"Display () const" <<endl;
cout<<"year:" <<_year<< endl;
cout<<"month:" <<_month<< endl;
cout<<"day:" <<_day<< endl<<endl;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
下面来一段绕口令:
构造函数体中的语句只能将其称作为赋初值。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。
注意:
class Date
{
public:
Date(int year, int month, int day) //以冒号开始,接着是逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
特别的:
5. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
6. 类中包含这些成员,必须放在初始化列表位置进行初始化:引用成员变量,const成员变量,自定义类型成员(该类没有默认构造函数)
7. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
8. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
explicit构造函数是用来防止隐式转换的。用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。
class Date
{
public:
Date(int year)
:_year(year)
{}
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month:
int _day;
};
C++11支持非静态成员变量在声明时进行初始化赋值,但这里不是初始化,这里是给声明的成员变量缺省值。
class B
{
public:
B(int b = 0)
:_b(b)
{}
int _b;
};
class A
{
public:
void Print()
{
cout << a << endl;
cout << b._b<< endl;
cout << p << endl;
}
private:
// 非静态成员变量,可以在成员声明时给缺省值。
int a = 10;
B b = 20;
int* p = (int*)malloc(4);
static int n;
};
int A::n = 10;
int main()
{
A a;
a.Print();
return 0;
}
友元分为:友元函数和友元类
在 C++ 中,一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。现在,我们来介绍一种例外情况——友元(friend)。借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
friend 的意思是朋友,或者说是好友,与好友的关系显然要比一般人亲密一些。我们会对好朋友敞开心扉,倾诉自己的秘密,而对一般人会谨言慎行,潜意识里就自我保护。在 C++ 中,这种友好关系可以用 friend 关键字指明,中文多译为“友元”,借助友元可以访问与其有好友关系的类中的私有成员。
为了方便使用cout,cin 这种输入输出,尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
class Date
{
friend ostream& operator<< (ostream& out,const Date& d); //友元函数,可以访问私有的
friend istream& operator>> (istream& in, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1) //构造函数
{
_year = year;
_month = month;
_day = day;
cout << "1" << endl;
}
private:
int _year; //对象的命名风格
int _month;
int _day;
};
ostream& operator<< (ostream & out,const Date&d) //设为全局,避免抢位置
{
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
istream& operator>> (istream& in, Date& d) //<<>>是输入输出运算符,所以重载后就用了
{
in>> d._year >> d._month >> d._day;
return in;
}
另外的:
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
友元关系不能传递。
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。
类和对象的基本概念的介绍就到此为止,希望大家多多指正,一键三连。
介绍完了类和对象的知识,就可以写自己的类了,下面以Date类为例做个示范:包括了构造函数,运算符的重载等。
.h文件
#pragma once
#include
#include
using std::cout;
using std::endl;
using std::cin;
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1); //1.构造函数,全缺省。
void Print(); //打印
//析构,拷贝构造,赋值重载,可以不写,默认生成
//stack才写
//运算符重载,日期加减
Date& operator+=(int day); //+=之后,day这个实体还是存在的,所以可以返回引用
Date operator+(int day);
Date& operator-=(int day);
Date operator-(int day);
//()为空是前置++,增加(int)是为了构成重载,后置++;
Date& operator++(); //++d 前置可以引用,因为是先加自己身上再返回
Date operator++(int); //d++ 用int占位,不需要给实参
Date& operator--();
Date operator--(int);
// 运算符重载
bool operator>(const Date& d)const;
bool operator<(const Date& d)const;
bool operator==(const Date& d)const;
bool operator<=(const Date& d)const;
bool operator>=(const Date& d)const;
bool operator!=(const Date& d)const;
// 日期-日期 返回天数
int operator-(const Date& d);
private: //私有
int _year;
int _month;
int _day;
};
.cpp
#include"Date.h"
inline int GetDay(int year, int month) //返回当月最大日期,频繁调用所以内联
{
static int days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; //12个月的日期,只创建一次,放在静态区
int day = days[month - 1];
if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) ) //判断闰年
{
day = 29;
}
return day;
}
Date::Date(int year, int month, int day) //指定类域,缺省函数只能在一个地方出现
{
if (year >= 0
&& month <= 12 && month >= 1
&& day > 0 && day <= GetDay(year, month)) //判断日期的合法性
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << " no " << endl;
assert(false);
}
}
void Date::Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
Date& Date::operator+=(int day) //
{
if (day < 0) //day<0
{
_day -= -day;
}
else
{
_day += day;
while (_day > GetDay(_year, _month)) //判断day是否合法,不合法就月份+1,月份超出就置1,年++;
{
cout << GetDay(_year, _month) << _month<<endl;
_day -= GetDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
}
return *this;
}
Date Date::operator+(int day) //需要创建一个新的对象,返回这个对象
{
Date ret(*this); //使用默认拷贝构造函数(浅拷贝)
ret += day; //直接进行复用,直接操作对象,不要操作成员。
return ret;
}
Date& Date::operator-=(int day)
{
if (day < 0) //减负数
{
_day += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
_month--; //先-月,因为要往上一个月算,
if (_month < 0) //逻辑参考+=
{
_month = 12;
_year--;
}
_day += GetDay(_year, _month);
}
}
return *this;
}
Date Date::operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
//Date:: 声明域
bool Date::operator>(const Date& d)const
{
if (_year < d._year)
{
return false;
}
else if (_year > d._year)
{
return true;
}
else
{
if (_month < d._month)
{
return false;
}
else if (_month > d._month)
{
return true;
}
else
{
if (_day > d._day)
{
return true;
}
else
{
return false;
}
}
}
}
bool Date::operator<(const Date& d)const
{
if (_year > d._year)
{
return false;
}
else if (_year < d._year)
{
return true;
}
else
{
if (_month > d._month)
{
return false;
}
else if (_month < d._month)
{
return true;
}
else
{
if (_day < d._day)
{
return true;
}
else
{
return false;
}
}
}
}
bool Date::operator==(const Date& d)const
{
if (*this > d || *this < d)
return false;
else
return true;
}
bool Date::operator<=(const Date& d)const
{
if (*this > d)
return false;
else
return true;
}
bool Date::operator>=(const Date& d)const
{
if (*this < d)
return false;
else
return true;
}
bool Date::operator!=(const Date& d)const
{
if (*this == d)
return false;
else
return true;
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date ret(*this);
*this += 1; //这里不能对ret操作,因为要做返回值,this实际上要加,但为了返回一个加之前,所以返回一个ret
return ret;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
int Date::operator-(const Date& d) //设置两端,然后遍历整个区间,++i就可以。
{
int flag = 1; //判断日期正负,默认为正
Date max = *this; //默认d小,如果不采取这种方式,下面while会存在编译错误
Date min = d;
if (*this < d)
{
max = d; //拷贝构造的另一种表达法,不能重复构建对象,重复定义根据就近原则会以上面的为最终结果
min = *this;
flag = -1;
}
int n = 0;
while (max > min)
{
++n;
++(min);
}
return n * flag; //大小*正负
}