面向对象的三大特征: 封装 继承 多态
物以类聚,人与群分
类:同一类事物(对象)共同特征提取出来,就形成了类。类是抽象的概念
对象:由类创建的具体实体
类中包括属性和行为
属性 称为成员变量 或者数据成员
行为 称为成员函数 或者成员方法
成员变量和成员函数 都称为类的成员
1.类的实现
#include
using namespace std;
class Computer{ //class是类的关键字 Computer是类名
public: //公共权限 ,类内和类外都可以访问
string brand;
string model;
void play_music(){
cout<<"《只因你太美》"<<endl;
}
void play_video(){
cout<<"《家有儿女》"<<endl;
}
void run_game(){
cout<<"《原神》"<<endl;
}
void show(){
cout<<"品牌:"<<brand<<" 型号:"<<model;
}
};
int main()
{
}
2.类的实例化
由类创建对象的过程叫类的实例化
对象分为两种:
1.栈内存对象 : 用.运算符调用成员,出了作用范围(两个花括号之间)自动销毁
2.堆内存对象: 用new关键字在堆区创建对象,对象类型的指针指向new关键字开辟的空间。使用->的方式调用成员。堆内存对象需要程序员手动销毁,如果不销毁这块内存区域会被持续占用,造成内存泄漏,造成程序的卡顿。所以需要用delete关键字进行销毁,并把对象指针置为NULL
#include
using namespace std;
class Computer{ //class是类的关键字 Computer是类名
public: //公共权限 类内和类外都可以访问
string brand;
string model;
void play_music(){
cout<<"《只因你太美》"<<endl;
}
void play_video(){
cout<<"《家有儿女》"<<endl;
}
void run_game(){
cout<<"《原神》"<<endl;
}
void show(){
cout<<"品牌:"<<brand<<" 型号:"<<model;
}
};
int main()
{
//栈内存对象
Computer c1; //对象名叫c1
c1.brand="联想";
c1.model="air14";
//由对象调用方法
c1.play_music();
c1.play_video();
c1.run_game();
c1.show();
//堆内存对象
Computer * c2=new Computer;
c2->brand="DELL";
c2->model="Dell3542";
c2->play_music();
c2->play_video();
c2->run_game();
c2->show();
delete c2; //销毁c2指向的堆内存空间
c2=NULL; //把对象指针置为NULL
}
3.封装
概念:通常情况下,会把类中的属性或者特征进行隐藏。通常是把类中的属性变为私有。如果外部访问需要公共接口,可以控制属性的读和写的权限。提高了程序的安全性
私有权限,只有类内可以访问,类的外部访问不到
#include
using namespace std;
//姓名 name 密码 password 地址 address
class Person{
private: //私有权限,只有类内可以访问
string name; //可读 可写
string password="123456"; //只写
string address; //只读
public:
void getName(){
cout<<"姓名是:"<<name<<endl;
}
void setName(string n){
name=n;
}
void setPassword(string p){
password=p;
}
void getAddress(){
cout<<"济南"<<endl;
}
};
int main()
{
Person p;
//p.name="小明"; //错误
p.setName("小明");
p.setPassword("123789");
p.getName();
p.getAddress();
}
4.构造函数
作用:用于创建对象时给属性值进行初始化
构造函数是个特殊的函数:
- 无需写返回值,不可以写void
- 构造函数与类同名
- 如果没有显示给出构造函数,编译器会给出默认的构造函数(参数为空,并且函数体为空)并没有实际操作,如果写出任意构造函数,编译器默认的构造函数就不存在
无参构造函数
#include
using namespace std;
class Computer{
private:
string brand;
string model;
int weight;
public:
void show(){
cout<<brand<<" "<<model<<" "<<weight<<endl;
}
//Computer(){} //编译器默认的构造函数
Computer(){
brand="HP";
model="暗影精灵";
weight=100;
}
};
int main()
{
Computer c1; //没有指定对象参数,会调用无参的构造函数
c1.show();
}
有参构造函数
有参构造函数让对象的创建更加灵活
#include
using namespace std;
class Computer{
private:
string brand;
string model;
int weight;
public:
void show(){
cout<<brand<<" "<<model<<" "<<weight<<endl;
}
//Computer(){} //编译器默认的构造函数
Computer(string b,string m,int w){
brand=b;
model=m;
weight=w;
}
};
int main()
{
Computer c1("联想","pro16",200);
c1.show();
}
构造函数支持函数重载
#include
using namespace std;
class Computer{
private:
string brand;
string model;
int weight;
public:
void show(){
cout<<brand<<" "<<model<<" "<<weight<<endl;
}
//构造函数支持重载
Computer(string b,string m,int w){
brand=b;
model=m;
weight=w;
}
Computer(){
brand="HP";
model="暗影精灵";
weight=100;
}
};
int main()
{
Computer c1("联想","pro16",200); //调用三个参数的构造函数
c1.show();
Computer * c2=new Computer; //调用无参构造函数
c2->show();
delete c2;
c2=NULL;
Computer * c3=new Computer("联想","air15",150);
c3->show();
delete c3;
c3=NULL;
}
构造函数支持函数默认值
同样遵循函数默认的注意事项
#include
using namespace std;
class Computer{
private:
string brand;
string model;
int weight;
public:
void show(){
cout<<brand<<" "<<model<<" "<<weight<<endl;
}
//构造函数支持重载
Computer(string b="HP",string m="暗影精灵",int w=100){
brand=b;
model=m;
weight=w;
}
};
int main()
{
Computer c1("联想","pro16",200); //调用三个参数的构造函数
c1.show();
Computer * c2=new Computer; //调用无参构造函数
c2->show();
delete c2;
c2=NULL;
Computer * c3=new Computer("联想","air15",150);
c3->show();
delete c3;
c3=NULL;
}
构造初始化列表
是构造函数简便写法 ,对象属性简单赋值时推荐初始化列表
#include
using namespace std;
class MobilePhone{
private:
string brand;
string model;
public:
//构造初始化列表
MobilePhone(string b,string m):brand(b),model(m){}
void show(){
cout<<brand<<" "<<model<<endl;
}
};
int main()
{
MobilePhone mp1("苹果","14promax");
mp1.show();
}
-
拷贝构造函数
概念:用已存在对象的值来初始化新的对象属性值
拷贝构造的特点:
- 拷贝构造函数与构造函数构成重载
- 如果不显示写出拷贝构造函数,编译器会给出默认的拷贝构造函数,完成对象之间的值复制。如果显示写出拷贝构造,编译器就不会提供默认的拷贝构造
拷贝构造函数的形式:
- 与类同名
- 参数是对象的引用或者const修饰对象的引用
对象之间相互独立的,对象之间属性也是相互独立的
#include
using namespace std;
class MobilePhone{
private:
string brand;
string model;
public:
MobilePhone(string b,string m):brand(b),model(m){}
//默认的拷贝构造功能与下方类似
//显示写出拷贝构造函数。编译器就不会给出默认的拷贝构造
MobilePhone(const MobilePhone& other){
cout<<"调用拷贝构造函数"<<endl;
brand=other.brand;
model=other.model;
}
void show(){
cout<<brand<<" "<<model<<endl;
}
};
int main()
{
MobilePhone mp1("苹果","14promax");
mp1.show();
cout<<"------------"<<endl;
MobilePhone mp2(mp1);
mp2.show();
cout<<&mp1<<" "<<&mp2<<endl; //0x61fe78 0x61fe70
}
深拷贝和浅拷贝
浅拷贝:编译器默认给出的拷贝构造函数,完成的就是浅拷贝。会完成对象之间简单的值复制。但是如果对象的属性有指针类型时,浅拷贝也只会简单的地址值的复制,这时两个对象的指针保存同一块地址,指向了同一块内存。破坏了对象之间的独立性
浅拷贝
#include
#include
using namespace std;
class MobilePhone{
private:
char * brand;
public:
MobilePhone(char * n){
brand=n;
}
MobilePhone(const MobilePhone& other){
brand=other.brand;
}
void show(){
//C++中cout后面跟的char * 会自动打印出字符串的内容
cout<<brand<<endl;
}
};
int main()
{
char a[20]="xiaomi";
MobilePhone mp1(a); //mp1.brand --->a
MobilePhone mp2(mp1); //mp2.brand--->a
mp1.show(); //xiaomi
mp2.show(); //xiaomi
strcpy(a,"redmi");
mp1.show(); //redmi
mp2.show(); //redmi
}
深拷贝
当对象属性有指针类型时,对象指针变量需要指向自己独立的区域,拷贝内容代替拷贝地址
#include
#include
using namespace std;
class MobilePhone{
private:
char * brand;
public:
MobilePhone(char * n){
brand=new char[20];
strcpy(brand,n);
}
MobilePhone(const MobilePhone& other){
brand=new char[20];
strcpy(brand,other.brand);
}
void show(){
//C++中cout后面跟的char * 会自动打印出字符串的内容
cout<<brand<<endl;
}
};
int main()
{
char a[20]="xiaomi";
MobilePhone mp1(a);
MobilePhone mp2(mp1);
mp1.show(); //xiaomi
mp2.show(); //xiaomi
strcpy(a,"redmi");
mp1.show(); //xiaomi
mp2.show(); //xiaomi
}
- 析构函数
析构函数:对象销毁前做善后清理工作。在对象销毁时,之前为成员变量开辟的内存空间需要进行释放
形式: ~类名() {}
析构函数特点:
- 与类同名 ,因为没有参数,所以不能重载
- 不显示给出析构函数,会有默认的析构函数,函数体为空。给出析构函数,编译器就不提供默认析构函数
- 对象销毁时自动调用
#include
#include
using namespace std;
class Cat{
private:
string name;
public:
Cat(string n):name(n){
cout<<name<<"诞生了"<<endl;
}
~Cat(){
cout<<name<<"挂掉了"<<endl;
}
};
void test(){
Cat c("Tom");
Cat * c2=new Cat("蓝猫"); //如果不delete销毁,堆对象空间不会释放,会造成内存泄漏
delete c2; //delete之后会自动调用析构函数
}
int main()
{
test();
cout<<"-----------"<<endl;
}
如果类中都是基本数据类型和string 类型,这时可以不显示写出析构函数,对象的数据会随着对象的销毁而销毁
当类中有指针类型的变量时候,这时需要写出析构函数,释放为指针变量开辟的空间
之前深拷贝,析构完善的代码:
#include
#include
using namespace std;
class MobilePhone{
private:
char * brand;
public:
MobilePhone(char * n){
brand=new char[20];
strcpy(brand,n);
}
MobilePhone(const MobilePhone& other){
brand=new char[20];
strcpy(brand,other.brand);
}
~MobilePhone(){
//[]指明,delete的指针指向的是数组形式的
delete [] brand;
}
void show(){
cout<<brand<<endl;
}
};
int main()
{
char a[20]="xiaomi";
MobilePhone mp1(a);
MobilePhone mp2(mp1);
mp1.show(); //xiaomi
mp2.show(); //xiaomi
strcpy(a,"redmi");
mp1.show(); //xiaomi
mp2.show(); //xiaomi
}
- 作用域限定符的使用
- 名字空间
命名空间实际上是由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间区域,把一些自己定义的变量、函数等标识符存放在这个空间中,从而与其他实体定义分隔开来。
std是C++标准库的一个名字空间,很多使用的内容都是来自于标准名字空间,例如字符串std::string、std::cout...
当项目中包含using namespace std;时,代码中使用std名字空间中的内容就可以省略前面的std::
#include
#include
using namespace std;
int a=20;
namespace mySpace {
int a=30;
}
int main()
{
int a=10;
cout<<a<<endl; //10
cout<<::a<<endl; //20 ::代表全局
cout<<mySpace::a<<endl; //30
}
7.2 函数声明和定义分离时
函数声明和定义分离时需要用,类名:: 指明函数属于哪个类,指明函数范围
void Student::setAge(int a)
void 返回值
Student:: 类的作用域限定符
setAge 函数名
#include
using namespace std;
class Student{
private:
string name;
int age;
public:
Student(string n,int a);
void setAge(int a);//类内声明
int getAge();
};
Student::Student(string n,int a){
name=n;
age=a;
}
void Student::setAge(int a){
age=a;
}
int Student::getAge(){
return age;
}
int main()
{
Student s("小强",20);
cout<<s.getAge()<<endl;
}
- explicit关键字
等号赋值时,等号左侧是对象类型,右侧恰好是对象构造函数所需要的类型,这时就会把右侧值传入到构造函数中,相当于隐式调用构造函数。
但是编码过程中,可能会不小心,隐式触发构造函数。造成错误,所以可以用explicit关键字屏蔽隐式构造
#include
using namespace std;
class Student{
private:
string name;
public:
explicit Student(string n);
void show();
};
void Student::show(){
cout<<name<<endl;
}
Student::Student(string n){
name=n;
}
int main()
{
Student s("小明");
s.show();
string str="小刚";
//Student s2=str; // 屏蔽隐式构造之后 ,这行就会报错
//s2.show();
}
- this指针
概念
this指针是个特殊的指针,存储是对象的首地址,成员函数都隐含一个this指针
#include
using namespace std;
class Test{
public:
void show(){
cout<<this<<endl;
}
};
int main()
{
Test t;
t.show(); //0x61fe8f
cout<<&t<<endl; //0x61fe8f
Test * t2=new Test;
t2->show();
cout<<t2<<endl; //0x6f17a8
delete t2; //0x6f17a8
t2=NULL;
}
原理
类的成员函数都隐含一个this指针。哪个对象调用成员函数,this指针就指向哪个对象,访问哪个对象的属性。虽然不用手写this指针,但是编译器都会使用this指针来调用成员
#include
using namespace std;
class Test{
private:
int a;
public:
Test(int n){
this->a=n;
}
void show(){
cout<<this->a<<endl;
}
void setA(int n){
this->a=n;
}
};
int main()
{
Test t(10);
Test t2(20);
t.show(); //10
t2.show(); //20
}
this指针应用
1.区分同名参数与属性
可以用this指针来区分属性 和 参数
#include
using namespace std;
class Student{
private:
string name;
int age;
public:
Student(string n,int a);
void show();
};
Student::Student(string name,int age){
name=name; //参数给参数
age=age;
}
void Student::show(){
cout<<name<<" "<<age<<endl;
}
int main()
{
Student s("小强",20);
s.show();
}
this指针区分属性和参数
#include
using namespace std;
class Student{
private:
string name;
int age;
public:
Student(string n,int a);
void show();
};
Student::Student(string name,int age){
this->name=name; //参数给参数
this->age=age;
}
void Student::show(){
cout<<name<<" "<<age<<endl;
}
int main()
{
Student s("小强",20);
s.show();
}
2.链式调用
当返回值是对象引用时,可以返回*this ,此函数支持链式调用
#include
using namespace std;
class Value{
private:
int n;
public:
Value(int n){
this->n=n;
}
void getValue(){
cout<<"结果:"<<n<<endl;
}
Value& addValue(int m){ //返回值是对象的引用
this->n+=m;
return *this;
}
};
int main()
{
Value v1(10);
v1.getValue();
v1.addValue(10).addValue(10).addValue(10);
v1.getValue(); //40
}
- static关键字
静态局部变量
static关键字修饰的局部变量。特点类中所有对象共享,所在函数第一次调用时初始化,程序结束时销毁
#include
using namespace std;
class Test{
public:
void testStatic(){
int a=1;
static int sa=1;
cout<<++a<<" "<<++sa<<endl;
}
};
int main()
{
Test t;
t.testStatic(); //2 2
t.testStatic(); //2 3
Test t2;
t2.testStatic();//2 4
}
静态成员变量
static关键字修饰的成员变量
- 必须得类内声明,类外初始化
- 所有对象共享,程序运行时创建,程序结束时销毁
- 公共权限下,除了用对象访问静态成员变量,也可以使用 类名::静态成员变量
#include
using namespace std;
class Test{
public:
//static int b=1; 错误
static int b;
};
int Test::b=1;
int main()
{
cout<<Test::b<<endl; //1 在对象没存在之前,静态成员变量就已经创建
Test t1;
cout<<t1.b<<endl; //1
t1.b++;
Test t2;
cout<<t2.b<<endl; //2
}
静态成员函数
static修饰的成员函数就是静态成员函数。
- 对象产生之前就可以调用。而普通成员函数必须需要对象来调用
- 静态成员函数只可以访问静态成员,不可以访问非静态成员。因为静态成员函数没有this指针
- 静态成员函数声明和定义分离时,static只需加在声明处
#include
using namespace std;
class Test{
public:
//static int b=1; 错误
static int b;
int a=10;
void test(){
cout<<"hello"<<endl;
}
//静态函数没有this指针
static void testStatic(){
//cout<a<
//this->test(); //普通成员函数,必须得对象来调用
cout<<b<<endl;
}
};
int Test::b=1;
int main()
{
Test::testStatic(); //1
}
11.const关键字
通常表示只读,不可修改。可以保证数据的安全
常局部变量
const修饰的局部变量。不可修改其数值
#include
using namespace std;
class Test{
public:
void func(const int a){
//a++; 只读不允许修改
cout<<a<<endl;
}
};
int main()
{
Test t;
int n=10;
t.func(n);
}
常成员变量
const修饰的成员变量
- 运行时其值不可以修改
- 必须通过初始化列表赋予初始值,不能通过构造函数方法体中赋值
#include
using namespace std;
class Test{
private:
const int constNum;
public:
//普通构造函数不可以给常成员变量初始化
// Test(int n){
// constNum=n;
// }
Test(int constNum):constNum(constNum){}
void show(){
//constNum++; //错误 只读不可以修改
cout<<constNum<<endl;
}
};
int main()
{
Test t(10);
t.show();
}
常成员函数
const修饰的成员函数
- 可以访问非const成员变量,但不可以更改变量值
- 不可以访问非const成员函数
#include
using namespace std;
class Test{
public:
int a=10;
void show(){
cout<<a<<endl;
a++;
cout<<a<<endl;
}
void show2() const
{
cout<<a<<endl;
//a++; //错误 const修饰的函数中不允许变量值
//show(); //错误 不可以访问非const函数
}
};
int main()
{
Test t;
t.show();
t.show2();
}
常对象
const修饰的对象
第一种方式: const 类名 对象名
第一种方式: 类名 const 对象名
常对象不允许修改属性值,只能访问const函数
#include
using namespace std;
class Test{
public:
int a=10;
void show(){
cout<<a<<endl;
a++;
cout<<a<<endl;
}
void show2() const
{
cout<<a<<endl;
}
};
int main()
{
const Test t;
//t.show(); //错误
t.show2();
Test const t2;
t2.show2();
}