类与对象
面向对象三大特性:封装,继承,多态
封装
例子
//感觉和java差不太多
const int PI = 3.14;
class circle {
//访问权限
public:
//属性
int radius; //半径
//方法
double calculateArea() {
return PI * radius * radius;
}
protected:
string name;
};
int main(int argc, char *argv[]) {
//实例化对象
circle c1;
c1.radius = 10;
cout << "圆的面积是:" << c1.calculateArea() << endl;
}
权限
public:类内可以访问 类外可以访问
protected:类内可以访问 类外不可以访问 子类可以访问
private:类内可以访问 类外不可以访问 子类不可访问
struct和class的区别
唯一区别为默认权限不同
struct默认权限为pulic
class默认权限为private
构造函数和析构函数简介
构造函数:类似java构造器,用于对象初始化
语法:类名(){}
析构函数:用于对象销毁前,执行一些清理工作
语法:~类名(){}
注意点:析构函数不可以有参数,所以也不可以重载
如果不提供构造和析构函数,编译器会提供空实现的构造和析构函数。
class person {
public:
person() {
cout << "person()" << endl;
}
~person() {
cout << "~person()" << endl;
}
};
void test() {
person p;
}
int main(int argc, char *argv[]) {
test(); //在栈上,执行完后,会释放这个对象也就会执行析构函数
}
构造函数
分类
按参数分:有参构造和无参构造
按类型分:普通构造和拷贝构造
class person1 {
public:
//拷贝构造函数
person1(const person1 &p) {
age = p.age; //将传入对象身上的所有属性,拷贝此对象上
}
private:
int age;
};
调用方式:
括号法
显示法
隐式转换法
class person1 {
public:
person1() {
}
person1(int age2) {
age = age2;
}
//拷贝构造函数
person1(const person1 &p) {
age = p.age; //将传入对象身上的所有属性,拷贝此对象上
}
private:
int age;
};
void test() {
//括号法
//注意调用默认构造函数的时候,不要加()
//person1 p() 会被认为是函数的声明 ,不会认为在创建对象
person1 p1(10);
person1 p2(p1); //调拷贝构造函数
//显示法
person1 p3;
person1 p4 = person1(10); //等号右侧称为匿名对象
//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
person1 p5 = person1(p4);
//注意点 不要利用拷贝构造函数 初始化匿名对象
//编译器会认为person1(p4) === person1 p4 是一个对象的声明
//person1(p4) 报错p4重定义
//隐式转换法
person1 p6 = 10; //相当于 person1 p6 = person1(10)
person1 p7 = p6; //拷贝构造
}
拷贝函数调用时机
1.创建
2.值传递的方式给函数参数传值
void work(person1 p) {
p.age = 100; //修改这里p的值,不影响原来的对象,
//只有创建对象的时候才会调用拷贝构造函数,到这边已经和构造函数无关了
cout<<"1"<<endl;
}
void test() {
person1 p;
work(p); //传值的时候会调用拷贝构造函数
cout<<p.age<<endl;
}
3.值方式返回局部对象
person1 work1() {
person1 p1;
return p1; //这边返回的不是p1是p1的拷贝
}
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝会带来的问题
class Person {
public:
int age;
int* height;
Person() {
}
Person(int age1,int height1) {
age = age1;
height = new int (height1);
}
~Person() {
if(height != NULL) {
delete height;//释放堆区数据
height = NULL;//防止野指针
}
}
};
void test() {
Person p1(51,190);
cout<<"p1age"<<p1.age<<"height"<<*p1.height<<endl;
Person p2(p1);
cout<<"p2age"<<p2.age<<"height"<<*p2.height<<endl;
}
int main(int argc, char *argv[]) {
test();
自带拷贝构造函数是浅拷贝,浅拷贝指针带来堆区内存重复释放
//Process finished with exit code -1073740940 (0xC0000374)
}
解决:使用深拷贝
//默认拷贝函数为浅拷贝
//自定义拷贝函数该为深拷贝
~Person() {
if(height != NULL) {
delete height;//释放堆区数据
height = NULL;//防止野指针
}
//添加后能正常结束程序
初始化列表
作用:初始化属性
语法:构造函数():属性1(值1),属性2(值2)...{}
感觉没啥用
class Person {
public:
int p_a;
int p_b;
int p_c;
//传统初始化操作
// Person(int a,int b,int c) {
// p_a = a;
// p_b = b;
// p_c = c;
// }
//初始化列表初始化属性
Person(int a,int b,int c):p_a(a),p_b(b),p_c(c) {
}
};
void test() {
Person p(10,20,30);
}
类对象作为类成员
构造的时候先构造类对象,再构造自身。析构的顺序与构造的顺序相反
静态成员
就是在成员变量和成员函数前加上关键字static,称为静态成员。
静态成员变量
所有对象共享同一份数据
编译阶段分配内存
类内声明,类外初始化
class Person {
public:
static int p_a; //类内声明
};
int Person::p_a = 100; //类外初始化操作
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
class Person {
public:
static void func() {
cout<<'1'<<endl;
}
};
void test() {
//通过对象访问
Person p;
p.func();
//通过类名访问
Person::func();
}
this指针
指向被调用的成员函数所属对象
this指针隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用
用途
当形参和成员变量同名时,可以用this来区分
Person(int age) {
this->age = age;
}
在类的非静态成员函数中返回对象本身,可使用return *this
class Person {
public:
int age;
string name;
Person(int age) {
this->age = age;
}
//返回本体要用引用的方式 不使用引用的方式即直接用 Person
//返回的是一个利用拷贝构造函数创建的新的Person对象
Person& PersonAddAge(Person &p) {
this->age += p.age;
return *this; //this是指向本体的指针所以 *this就是本体
}
};
void test02() {
Person p(10);
Person p1(20);
//链式编程思想
p1.PersonAddAge(p).PersonAddAge(p);//当返回的对象是本身可以一直追加这个操作
cout<<p1.age<<endl;
}
.和->的区别
访问结构的成员时使用点运算符,而通过指针访问结构的成员时,则使用箭头运算符
空指针访问成员函数
const修饰成员函数
常函数:
成员函数名后加const后就是常函数,常函数不可以修改成员属性,成员属性声明时加关键字mutable后在常函数中依然可以修改。
class Person {
public:
void showPerson() const{
m_B = 100;
//this 等于是一个指针常量 指针的指向是不可以修改的
//在成员函数后加this相当于修饰的是this指针 使得指针指向的内容也不可修改了
//const Person * const this
//m_A = 100; //这里其实等于 this->m_A = 100;
}
int m_A;
mutable int m_B; //仍然想修改加mutable
};
常对象
声明对象前加const就是常对象,只能调用常函数
友元
能让一个函数或者类访问另一个类中的私有成员
关键字:friend
全局函数做友元
class Building {
friend void GoodFriend(Building *building);
public:
Building() {
sittingRoom = "keting";
bedRoom = "woshi";
}
string sittingRoom;
private:
string bedRoom;
};
void GoodFriend(Building *building) {
cout<<building->sittingRoom<<endl;
cout<<building->bedRoom<<endl;
}
类做友元
class Building {
friend class goodGay;
public:
Building();
string sittingRoom;
private:
string bedRoom;
};
//类外写成员函数
Building::Building() {
sittingRoom = "keting";
bedRoom = "woshi";
}
class goodGay {
public:
Building *b;
void visit();
goodGay();
};
goodGay::goodGay() {
b = new Building;
}
void goodGay::visit() {
cout<<"fangwen" << b->sittingRoom<<endl;
cout<<"fangwen"<<b->bedRoom<<endl;
}
void test2() {
goodGay g;
g.visit();
}
成员函数做友元
class Building;
class goodGay {
public:
Building *b;
void visit();
void visit2();
goodGay();
};
class Building {
friend void goodGay::visit();
public:
Building();
string sittingRoom;
private:
string bedRoom;
};
//类外写成员函数
goodGay::goodGay() {
b = new Building;
}
void goodGay::visit() {
cout<<"fangwen" << b->sittingRoom<<endl;
cout<<"fangwen"<<b->bedRoom<<endl;
}
void goodGay::visit2() {
cout<<"fangwen" << b->sittingRoom<<endl;
//cout<<"fangwen"<<b->bedRoom<<endl;
}
Building::Building() {
sittingRoom = "keting";
bedRoom = "woshi";
}
void test2() {
goodGay g;
g.visit();
}
运算符重载
对已有运算符重新定义,以适应不同数据类型
加法运算符重载
法1:成员函数重载
class Person {
public:
int a;
int b;
Person(){}
Person(int a,int b){
this->a = a;
this->b = b;
}
//成员函数重载+号
Person operator+(Person &p) {
Person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
};
void test() {
Person a = Person(5,6);
Person b = Person(7,8);
//成员函数重载本质 Person c = a.operator+(b);
Person c = a + b ;
}
法2:全局函数重载
class Person {
public:
int a;
int b;
Person(){}
Person(int a,int b){
this->a = a;
this->b = b;
}
};
// 全局函数重载+号
Person operator+(Person &p1,Person &p2) {
Person temp;
temp.a = p1.a +p2.a;
temp.b = p1.b + p2.b;
return temp;
}
void test() {
Person a = Person(5,6);
Person b = Person(7,8);
//全局函数重载本质 Person c = operator+(a,b)
Person c = a + b ;
}
左移运算符重载
用来输出对象
不会用成员函数重载<<运算符,因为无法实现cout在左侧
ostream& operator<<(ostream &cout,Person &p) {
cout<<"p.a = "<<p.a<<"p.b = "<<p.b;
return cout; //还是链式编程的思想
}
void test() {
Person a = Person(5,6);
cout<<a<<endl; //不反回cout的话后面没法继续输出
}
递增运算符重载
class MyInterge {
friend ostream& operator<<(ostream &cout,const MyInterge &myint);
public:
//重载前置++运算符
MyInterge& operator++() { //返回引用是为一直对同一个数据进行操作
++m_Num; //换句话来说,没有引用返回的就是新的对象
return *this;
}
//重载后置++运算符
//void operator++(int) 这里的int是一个占位参数,用来区分前置和后置递增
MyInterge operator++(int) //只能是int
{//后置++运算符要返回值 因为这里的temp是局部变量,函数结束后就释放掉了
//先 记录当时结果
MyInterge temp = *this;
//后 递增
++m_Num;
//最后将记录结果返回
return temp;
}
MyInterge() {
m_Num = 0;
}
private:
int m_Num;
};
注意这里后置递增是不满足链式法的
赋值运算符重载
用来避免浅拷贝带来的问题
class Per {
public:
int* age;
Per& operator=(Per &p) {
//编译器提供的是浅拷贝
//age = p.age;
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if(age!=NULL) {
delete age;
age = NULL;
}
age = new int(*p.age);//深拷贝
return *this;
}
Per(int a) {
age = new int(a);
}
~Per() {
if(age!=NULL) {
delete age;
age =NULL;
}
}
};
void test03() {
Per p1 = 17;
Per p2 = 19;
p1 = p2;
}
关系运算符(==,!=,等)的重载和上述类似,就不多说了
函数调用运算符的重载
函数调用运算符重载后的使用方式非常像函数的调用,因此称为仿函数。
再STL中常用这里先不细说。