目录
1.面向对象程序设计思想
面向对象程序设计思想有三大特征:封装,继承,多态。
1.封装
封装是面向对象思想最重要的特征,封装就是隐藏,它将数据和数据处理过程封装成一个独立的模块,将对象的属性和行为都封装起来,对外提供接口,不需要外界知道具体的实现细节。
2.继承
继承主要描述的是类与类之间的关系,通过继承无须重新编写原有类,就能对原有类进行扩展,继承不仅增强了代码复用性,提高了开发效率,为程序维护提供便利。
3.多态
多态是指事物的多种表现形态,多态就是不同的对象同一信息产生不同的行为,多态使得开发更科学,更符合人类的思维习惯,提高软件可靠性。
2.初识类和对象
1.类的定义
类是对象的抽象,是一种自定义数据类型,用于描述一组对象的共同属性和行为。类的定义如下:
class 类名
{
权限控制符:
成员;
};
(1)class是类定义的关键字。
(2)类名是类的标识符,符合命名规范,
(3)类名后面的大括号,用于包含类的成员,类的所有成员要在大括号中声明,类可以定义成员变量和成员方法,成员变量描述对象的属性,成员函数描述对象的行为。
(4)声明类的成员时,要使用权限控制符限定成员的访问规则,包括public,protected,private,权限依次递减。
(5)大括号后的‘;’表示类定义的结束。
class Student
{
public:
void study();
void exam();
private:
string _name;
int _age;
};
通常类的成员函数在类中声明,在类外实现,在类外实现成员,需在返回值后函数名前加上所属的类作用域,表示函数属于哪个类。
void Student::study()
{
cout<<"学习C++"<<endl;
}
void Student::exam()
{
cout<<"C++考试成绩100分"<<endl;
}
- public(公有类型):被public修饰的成员称为公有成员,可以被所属类的成员函数,类对象,派生类对象,友元函数,友元类访问。
- protected(保护类型):可以被所属类的成员函数,派生类对象,友元类和友元函数访问。
- private(私有类型):只能被所属类的成员函数,友元函数,友元类访问。
2.对象的创建和使用
对象的定义格式如下:
类名 对象名;
下面创建一个学生类的对象:
Student stu;
创建了类的对象stu之后,系统要为对象分配内存空间,用于存储对象成员,每个对象都有成员变量和成员方法,创建两个Student类对象stu1和stu2,每个学生的属性都不同,在创建对象时应当为每个对象分配独立的空间存储成员变量,c++用同一块空间存放同类对象的成员函数代码,每个对象调用同一段代码。
对象的成员变量和成员函数的访问可以通过‘.’运算符实现,格式如下:
对象名.成员变量
对象名.成员函数
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
void study();
void exam();
string _name;
int _age;
};
void Student::study()
{
cout<<"学习C++"<<endl;
}
void Student::exam()
{
cout<<"C++考试成绩100分"<<endl;
}
int main()
{
Student stu;
stu._name="liu";
stu._age=20;
cout<<stu._name<<stu._age<<endl;
stu.study();
stu.exam();
return 0;
}
liu20
学习C++
C++考试成绩100分
可以使用new创建类对象
Student* ps=new Student;
delete ps;
3.封装
C++中的封装是通过类实现的,通过类把具体事物抽象成一个由属性和行为结合的独立单位,类的对象会表现出具体的属性和行为,封装是隐藏对象的内部实现细节,只对外提供访问的接口。
通过权限控制符可以限制外界对类的成员变量的访问,将对象的状态信息隐藏在对象内部,通过类实现的接口实现对类中成员的访问。
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
void study();
void exam();
void setName(string name);
void setAge(int age);
string getName();
int getAge();
private:
string _name;
int _age;
};
void Student::study()
{
cout<<"学习C++"<<endl;
}
void Student::exam()
{
cout<<"C++考试成绩100分"<<endl;
}
void Student::setName(string name)
{
_name=name;
}
void Student::setAge(int age)
{
if(age<0||age>100)
{
cout<<_name<<"年龄输入错误"<<endl;
_age=0;
}
else
_age=age;
}
string Student::getName()
{
return _name;
}
int Student::getAge()
{
return _age;
}
int main()
{
Student stu;
stu.setName("liu");
stu.setAge(-20);
cout<<stu.getName() << stu.getAge()<<"岁"<<endl;
stu.study();
stu.exam();
stu.setName("zhang");
stu.setAge(20);
cout<<stu.getName() << stu.getAge()<<"岁"<<endl;
stu.study();
stu.exam();
return 0;
}
liu年龄输入错误
liu0岁
学习C++
C++考试成绩100分
zhang20岁
学习C++
C++考试成绩100分
4.this指针
在上面的例子中,getName函数可以区分是哪一个对象调用它,这是通过this指针实现的,
this指针是C++实现封装的一种机制,他将对象和对象调用的非静态成员函数联系在一起,当创建一个对象时,编译器会初始化一个this指针,指向创建的对象,this指针不存储在对象内部,而是作为所以非静态成员函数的参数。作为类的成员函数时,如果形参与类的属性重名,也可以用this指针解决。
void Student::setName(string name)
{
this->_name=name;
}
void Student::setAge(int age)
{
if(age<0||age>100)
{
cout<<_name<<"年龄输入错误"<<endl;
_age=0;
}
else
this->_age=age;
}
当类的成员返回值为一个对象,则可以使用return* this返回对象本身。
5.构造函数
构造函数是类的特殊成员函数,用于初始化对象,构造函数在创建对象时由编译器自动调用。C++的类中至少要有一个构造函数,如果类中没有定义。系统会提供一个默认的无参构造函数,函数体为空,因此一般要显示定义构造函数。
1.自定义构造函数
class 类名
{
权限控制符:
构造函数名(参数列表)
{
函数体
}
//其它成员
};
格式要求如下:
- 构造函数名必须与类名相同
- 构造函数名前不需要返回类型
- 构造函数无返回值
- 构造函数一般为public
如果在类中提供了自定义构造函数,编译器不再提供默认构造函数。
1.自定义无参构造函数
自定义无参构造函数,可以在函数内部直接给成员变量赋值:
#include <iostream>
#include <iomanip>
using namespace std;
class Clock
{
public:
Clock();//自定义无参构造
void showTime();
private:
int _hour;
int _min;
int _sec;
};
Clock::Clock()
{
_hour=0;
_min=0;
_sec=0;
}
void Clock::showTime()
{
cout<<setw(2)<<setfill('0')<<_hour<<":"
<<setw(2)<<setfill('0')<<_min<<":"
<<setw(2)<<setfill('0')<<_sec<<endl;
}
int main()
{
Clock clock;
cout<<"clock:";
clock.showTime();
return 0;
}
clock:00:00:00
2.自定义有参构造函数
如果希望在创建对象时提供有效的初始值,可以通过定义有参构造函数实现。
#include <iostream>
#include <iomanip>
using namespace std;
class Clock
{
public:
Clock(int hour,int min,int sec);//自定义有参构造
void showTime();
private:
int _hour;
int _min;
int _sec;
};
Clock::Clock(int hour,int min,int sec)
{
_hour=hour;
_min=min;
_sec=sec;
}
void Clock::showTime()
{
cout<<setw(2)<<setfill('0')<<_hour<<":"
<<setw(2)<<setfill('0')<<_min<<":"
<<setw(2)<<setfill('0')<<_sec<<endl;
}
int main()
{
Clock clock1(10,20,30);
cout<<"clock1:";
clock1.showTime();
Clock clock2(22,16,12);
cout<<"clock2:";
clock2.showTime();
return 0;
}
clock1:10:20:30
clock2:22:16:12
可以使用列表初始化实现成员变量初始化
Clock::Clock(int hour,int min,int sec):_hour(hour),_min(min),_sec(sec)
{
}
2.重载构造函数
在C++,构造函数允许重载。
class Clock
{
public:
Clock();
Clock(int hour);
Clock(int hour,int min);
Clock(int hour,int min,int sec);
private:
int _hour;
int _min;
int _sec;
};
当定义具有默认参数的重载构造函数时,要防止调用的二义性。
Clock::Clock(int hour,int min):_hour(hour),_min(min)
{
cout<<"调用两个参数的构造函数"<<endl;
_sec=10;
}
Clock::Clock(int hour, int min, int sec=0)
{
cout<<"调用三个参数的构造函数"<<endl;
_hour=hour;
_min=min;
_sec=sec;
}
3.含有成员对象的类的构造函数
C++允许将一个对象作为另一个类的成员变量,即类中的成员变量可以是其他类的对象,称为类的子对象或成员对象。
class B
{
A a;
}
创建含有成员对象的对象时,先执行成员对象的构造函数,再执行类的构造函数,创建B对象时,先执行类A的构造函数,再执行类B的构造函数,如果类A构造函数有参数,其参数要从类B的构造函数中传入,且必须以列表初始化。
#include <iostream>
using namespace std;
class Birth
{
public:
Birth(int year,int month,int day);
void show();
private:
int _year;
int _month;
int _day;
};
Birth::Birth(int year, int month, int day)
:_year(year),_month(month),_day(day)
{
cout<<"Birth类构造函数"<<endl;
}
void Birth::show()
{
cout<<"出生日期"<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
class Student
{
public:
Student(string name,int id,int year,int month,int day);
void show();
private:
string _name;
int _id;
Birth birth;
};
Student::Student(string name, int id, int year, int month, int day)
: birth(year,month,day)
{
cout<<"Student类构造函数"<<endl;
_name=name;
_id=id;
}
void Student::show()
{
cout<<"姓名:"<<_name<<endl;
cout<<"学号:"<<_id<<endl;
birth.show();
}
int main()
{
Student stu("lili",1002,2000,1,1);
stu.show();
return 0;
}
Birth类构造函数
Student类构造函数
姓名:lili
学号:1002
出生日期2000-1-1
6.析构函数
在C++中,对象资源的释放通过析构函数完成,析构函数的作用是在对象被释放之前完成一些清理工作,调用完成后,对象占用的资源也被释放。与构造函数一样,析构函数也是类的一个特殊成员函数,格式如下:
class 类名
{
~析构函数名();
...
}
- 析构函数的名称与类名相同,在析构函数名称前加‘~’符号。
- 析构函数没有参数,所以不能重载。
- 析构函数没有返回值,不能在内部return任何值。
当程序结束时,编译器会自动调用析构函数完成清理工作,如果类中没有定义,编译器提供一个默认的析构函数,它只能完成栈内存对象的清理,不能完成堆内存的清理,往往要自己定义析构函数,函数中定义对象,在函数调用结束时,调用析构清理,static修饰对象在程序结束时调用析构,new创建对象,在delete时调用析构。
析构函数的调用顺序与构造函数的调用顺序是相反的,先构造的后析构,后构造的先析构。
#include <iostream>
#include <cstring>
using namespace std;
class Rabbit
{
public:
Rabbit(string name,const char* pf);
void eat();
~Rabbit();
private:
string _name;
char* _food;
};
Rabbit::Rabbit(string name, const char *pf)
{
cout<<"调用构造函数"<<endl;
_name=name;
_food=new char[50];//为food指针申请空间
memset(_food,0,50);//初始化_food空间
strcpy(_food,pf);//将pf指向的数据复制到_food中
}
void Rabbit::eat()
{
cout<<_name<<" is eating "<<_food<<endl;
}
Rabbit::~Rabbit()
{
cout<<"调用析构函数,析构"<<_name<<endl;
if(_food!= nullptr)
delete []_food;
}
int main()
{
Rabbit A("A","luobo");
A.eat();
Rabbit B("B","baicai");
B.eat();
return 0;
}
调用构造函数
A is eating luobo
调用构造函数
B is eating baicai
调用析构函数,析构B
调用析构函数,析构A
7.拷贝构造函数
在程序中经常使用已有对象完成新对象的初始化,通过拷贝构造完成。
1.拷贝构造函数的定义
拷贝构造函数是一种特殊的构造函数,具有构造函数的所以性质,使用本类对象的引用作为形参,能够通过一个已经存在的对象初始化该类的另一个对象。格式如下:
class 类名
{
public:
构造函数名称(const 类名& 对象名)
{
函数体
}
}
在定义拷贝构造函数时,为了使引用的对象不被修改,通常用const修饰引用的对象。
#include <iostream>
using namespace std;
class Sheep
{
public:
Sheep(string name,string color);
Sheep(const Sheep& another);
void show();
~Sheep();
private:
string _name;
string _color;
};
Sheep::Sheep(string name,string color)
{
cout<<"调用构造函数"<<endl;
_name=name;
_color=color;
}
Sheep::Sheep(const Sheep& another)
{
cout<<"调用拷贝构造函数"<<endl;
_name=another._name;
_color=another._color;
}
void Sheep::show()
{
cout<<_name<<" "<<_color<<endl;
}
Sheep::~Sheep()
{
cout<<"调用析构函数"<<endl;
}
int main()
{
Sheep sheepA("Dolly","white");
cout<<"sheepA:";
sheepA.show();
Sheep sheepB(sheepA);
cout<<"sheepB:";
sheepB.show();
return 0;
}
调用构造函数
sheepA:Dolly white
调用拷贝构造函数
sheepB:Dolly white
调用析构函数
调用析构函数
当涉及对象之间的赋值时,编译器会自动调用拷贝构造函数。
- 使用一个对象初始化另一个对象。
- 对象作为参数传递给函数。
- 函数返回值为对象。
2.浅拷贝
如果没有定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数,只能完成简单的赋值操作,无法完成堆内存成员数据的拷贝,类中含有指针时,只是将新对象的指针指向原有对象指针指向的内存,并没有申请新空间,称为浅拷贝。
#include <iostream>
#include <cstring>
using namespace std;
class Sheep
{
public:
Sheep(string name,string color,const char* home);
Sheep(const Sheep& another);
void show();
~Sheep();
private:
string _name;
string _color;
char* _home;
};
Sheep::Sheep(string name,string color,const char* home)
{
cout<<"调用构造函数"<<endl;
_name=name;
_color=color;
int len=strlen(home)+1;
_home=new char[len];
memset(_home,0,len);
strcpy(_home,home);
}
Sheep::Sheep(const Sheep& another)
{
cout<<"调用拷贝构造函数"<<endl;
_name=another._name;
_color=another._color;
_home=another._home;
}
void Sheep::show()
{
cout<<_name<<" "<<_color<<_home<<endl;
}
Sheep::~Sheep()
{
cout<<"调用析构函数"<<endl;
if(_home!= nullptr)
delete []_home;
}
int main()
{
const char* p="Beijing";
Sheep sheepA("Dolly","white",p);
cout<<"sheepA:";
sheepA.show();
Sheep sheepB(sheepA);
cout<<"sheepB:";
sheepB.show();
return 0;
}
运行上面的程序,会抛出异常,在浅拷贝过程中,对象sheepA中的_home指针和sheepB中_home指针指向同一块内存空间,析构函数先析构B,后析构A,重复释放同一块内存空间,发生异常。
3.深拷贝
深拷贝会为新对象中的指针分配新的内存空间,将数据复制到新空间。
Sheep::Sheep(const Sheep& another)
{
cout<<"调用拷贝构造函数"<<endl;
_name=another._name;
_color=another._color;
int len=strlen(another._home)+1;
_home=new char[len];
strcpy(_home,another._home);
}
8.关键词修饰类的成员
1.const修饰类的成员
1.const修饰成员变量
使用const修饰的成员变量称为常成员变量,只可以读取第一次初始化的数据,之后不能修改。
2.const修饰成员函数
修饰成员函数时,const位于成员函数的后面,在函数内部,只能访问类的成员变量,不能修改,而且只能调用类的常成员函数,类中定义的成员函数若与常成员函数名相同则构成重载,常成员函数只能由const修饰的对象进行访问。
2.static修饰类的成员
1.static修饰成员变量
static修饰的静态成员变量只能在类内部定义,在类外初始化,可以通过对象和类进行访问,计算类的大小时不包含其中。
2.static修饰成员函数
使用static修饰的成员函数,同静态成员变量一样可以通过对象或类调用,静态成员函数可以访问类中的静态成员变量和静态成员函数,静态成员函数属于类,不属于对象,没有this指针。
9.友元
1.友元函数
友元函数可以是类外定义的函数或者是其他类中的成员函数,在类中声明某一函数为友元函数,则函数可以访问类中所有数据。
1.普通函数作为友元函数
class 类名
{
friend 返回值 友元函数名(形参列表);
}
#include <iostream>
using namespace std;
class Circle
{
friend void getArea(Circle &circle);
private:
float _radius;
const float PI=3.14;
public:
Circle(float radius);
~Circle();
};
Circle::Circle(float radius):_radius(radius)
{
cout<<"初始化圆的半径"<<_radius<<endl;
}
Circle::~Circle() {}
void getArea(Circle &circle)
{
cout<<"圆的半径是:"<<circle._radius<<endl;
cout<<"圆的面积是"<<circle.PI*circle._radius*circle._radius<<endl;
cout<<"友元函数修改半径"<<endl;
circle._radius=1;
cout<<"圆的半径是:"<<circle._radius<<endl;
}
int main()
{
Circle circle(10);
getArea(circle);
return 0;
}
2.其他类的成员函数作为友元函数
其他类中的成员函数作为本类的友元函数时,需要在本类中表明该函数的作用域,并添加友元函数所在类的前向声明。
class B;
class A
{
public:
int func();
};
class B
{
friend int A::func();
};
2.友元类
可以将一个类声明为友元类,友元类可以声明在类中任意位置,声明友元类之后,友元类中的所有成员函数都是该类的友元函数,能够访问该类的所有成员。
class B;
class A
{
};
class B
{
friend class A;
};
#include <iostream>
using namespace std;
class Time
{
public:
Time(int hour,int minute,int second);
friend class Date;
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year,int month,int day);
void showTime(Time& time);
private:
int _year;
int _month;
int _day;
};
Date::Date(int year, int month, int day)
{
_year=year;
_month=month;
_day=day;
}
void Date::showTime(Time &time)
{
cout<<_year<<"-"<<_month<<"-"<<_day
<<" "<<time._hour<<":"<<time._minute
<<":"<<time._second<<endl;
}
Time::Time(int hour, int minute, int second)
{
_hour=hour;
_minute=minute;
_second=second;
}
int main()
{
Time time(17,30,20);
Date date(2019,10,31);
date.showTime(time);
return 0;
}
2019-10-31 17:30:20
- 友元声明位置不受权限控制符的影响。
- 友元是单向的。
- 友元不具有传递性。
- 友元关系不能被继承。
持续更新中............