一、概述:
c++面向对象的三大特征:封装,继承,多态
c++中,万物皆可为对象,对象有其属性和行为
具有相同性质的对象,抽象称为类,人属于人类,车属于车类
二、封装
1、封装的意义:
1、将属性和行为作为一个整体,表现生活中的事物
2、将属性和行为加以权限控制
封装的意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限:属性 / 方法}:
示例1:设计一个圆类,求圆的周长
代码:
const double PI = 3.14;
//创建一个圆类,求周长
class circle {
public:
//属性-半径
double c_r;
//行为-求周长
double c_zc() {
return 2 * PI * c_r;
}
};
int main() {
//创建一个圆类的对象
circle yuan1;
yuan1.c_r = 10;
//2*PI*10 = 62.8
cout << "圆的周长:" << yuan1.c_zc() << endl;
system("pause");
return 0;
}
测试效果图
封装的意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1、public 公共权限—成员类内可以访问,类外也可以访问
2、protected 保护权限—成员类内可以访问,类外不可以访问,子类可以访问父类保护的内容
3、private 私有权限—成员类内可以访问,类外不可以访问,子类不可以访问父类私有内容
权限
类内函数
类外对象
子类
友元
public
可
可
可
可
protected
可
不可
可
可
private
可
不可
不可
可
示例:
注意事项:
1、如果声明不写 public、protected、private,则默认为 private;
2、每个限定符的有效范围从出现到另一个限定符或类结束为止。但为了使程序清晰,应该使每种限定符只出现一次。
2、struct和class的区别
C++中,struct和class的唯一区别:默认访问权限不同
区别:
1、struct 默认权限为公共
2、class默认权限为私有
3、成员属性设置为私有
优点:
1、将所有成员属性设置成私有,可以自己控制读写权限
2、对于写权限,我们可以检测数据的有效性
示例:
//成员设为私有
class Person {
//公共的读写函数
public:
//name
void setName(string& name){
p_name = name;
}
string showName() {
return p_name;
}
//age
int showAge() {
p_age = 18;
return p_age;
}
//lover
void setLover(string& lover) {
p_lover = lover;
}
//私有属性
private:
string p_name; //可读可写
int p_age = 0; //可读
string p_lover; //可写
};
int main() {
Person p1;
string name = "zhangsan";
p1.setName(name);
cout << p1.showName() << endl;
//p1.setAge();
cout << p1.showAge() << endl;
string lover = "cuihua";
p1.setLover(lover);
//cout << p1.showLover() << endl;
}
三、对象的初始化和清理
1、构造函数和析构函数
对象的初始化和清理,由构造函数和析构函数完成,如果我们不提供构造和析构,编译器会提供,不过这里的构造和析构是空实现的。
构造函数:作用于创建对象时,为对象成员属性赋值
析构函数:作用于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
1、没有返回值,也不写void
2、函数名称与类名相同
3、可以有参数,可以重载
4、程序调用对象时自动调用构造函数,且只会调用一次
析构函数语法:~类名(){}
1、没有返回值,不写void
2、函数名和类名相同,名称前加符号~
3、不可以有参数,不可以重载
4、程序对象销毁前时自动调用构造函数,且只会调用一次
示例:
class Person {
public:
//构造函数
Person() {
cout << "构造函数调用!" << endl;
}
//析构函数
~Person() {
cout << "析构函数调用!" << endl;
}
};
void test() {
Person p; //将对象创建在栈区,便于调用析构
}
int main() {
test(); //调用创建对象函数
system("pause");
return 0;
}
2、构造函数的分类
两种分类方式:
1、按参数分为,有参构造和无参构造
2、按类型分为:普通构造和拷贝构造
注意事项:
1、调用默认构造函数时,不要加(),因为会被编译器认为是函数的声明
三种调用方式
1、括号法
2、显示法
3、隐式转换法
个人推荐使用括号法,可读性很好~~~
注意事项:
1、不要利用拷贝构造函数,初始化匿名对象,因为编译器会认为Person (p3) == Person p3;
,被认为是一个对象重定义
3、拷贝函数的调用时机
c++拷贝构造函数调用时机通常有三种情况:
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数的形参传值
3、以返回值的方式,返回局部变量(对vs高版本不适用,只会调用默认构造,没有拷贝构造,可能对其语法有所优化)
示例:
4、构造函数的调用规则
c++编辑器默认给一个类提供三个函数:
1、默认构造函数(空实现)
2、默认析构函数(空实现)
3、默认拷贝构造函数(值拷贝)
规则1、
如果用户有定义由有参构造,c++不再提供默认无参构造,但会提供拷贝构造
规则2、
若用户定义拷贝构造函数,c++不会再提供其他构造函数
示例:
5、深拷贝和浅拷贝
辨析:
1、浅拷贝:简单的赋值拷贝操作
2、深拷贝:在堆区重新申请空间,进行拷贝操作
示例:
//深拷贝和浅拷贝
class Person {
public:
Person() {
cout << "无参构造函数调用" << endl;
}
Person(int a,int hgt) {
age = a;
height = new int(hgt);
cout << "有参构造函数调用" << endl;
}
~Person() {
if (height != NULL) {
delete height;
height = NULL;
}
cout << "析构函数调用" << endl;
}
//新定义一个深拷贝构造函数,程序就会正常
//如果删除这个函数,程序会报错
Person(Person& p) {
age = p.age;
height = new int(*p.height); //重新分配一个堆内存给拷贝的对象
cout << "拷贝构造函数调用" << endl;
}
int age;
int* height;
};
void test1() {
Person p1(18,180);
Person p2(p1);
cout << "p1年龄:" << p1.age << "p1身高:" << *p1.height << endl;
cout << "p2年龄:" << p2.age << "p2身高:" << *p2.height << endl;
}
int main() {
test1();
system("pause");
return 0;
}
总结:
1、编译器提供的默认拷贝函数是浅拷贝
2、浅拷贝带来的问题就是堆区的内存重复释放
3、浅拷贝的问题,可以利用深拷贝解决(自定义一个深拷贝函数)
6、初始化列表
作用:利用初始化列表语法,来初始化属性
语法:
构造函数():属性1 (值1) ,属性2(值2)...{
//构造函数属性和方法
}
示例:
//初始化列表
class Person {
public:
////传统初始化法
//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) {
}
int p_a;
int p_b;
int p_c;
};
void test1() {
Person p1(10, 20, 30);
/*cout <<"传统方法"<< p1.p_a << p1.p_b << p1.p_c << endl;*/
cout <<"初始化列表方法:"<< p1.p_a << p1.p_b << p1.p_c << endl;
}
int main() {
test1();
system("pause");
return 0;
}
总结:
两种方法进行属性初始化都是可行了,但初始化列表整体上更简洁,更推荐使用~
7、类对象作为类成员
类中的成员是另一个类的对象,我们称其为 对象成员
例如:
class A{}
class B{
A a;
}
B类中存在A类定义的对象,A为对象成员
那么在创建B对象时,A与B的构造和析构的顺序是谁先谁后?
答:
1、构造:先构造对象成员的对象,再构造自身的对象;
2、析构:先析构自身的对象,再析构对象成员的对象
示例:
//对象成员
//手机类
class Phone {
public:
Phone(string pName) :p_name(pName) {
cout << "Phone类构造调用" << endl;
}
~Phone(){
cout << "Phone类析构调用" << endl;
}
string p_name;
};
//人类
class Person {
public:
Person(string name,string pName) :p_name(name),p_N(pName) {
cout << "Person类构造调用" << endl;
}
~Person() {
cout << "Person类析构调用" << endl;
}
string p_name;
Phone p_N;
};
void test() {
Person p("张三","HUAWEi");
cout << "姓名:" << p.p_name << "手机:" <<p.p_N.p_name<< endl;
}
int main() {
test();
system("pause");
return 0;
}
8、静态成员
static关键字修饰的成员就是静态成员
静态成员分为两类:
1、静态成员变量
所有对象共享同一份数据
在编译阶段分配内存(全局区)
类内声明,类外初始化
2、静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
示例1:静态成员变量
//静态成员变量
class Person {
public:
static int s_A; //类内声明
};
int Person::s_A = 10;//类外定义
void test() {
//访问1:对象访问
Person p;
cout << p.s_A << endl;
p.s_A = 20;
//访问2:类名访问
cout << Person::s_A << endl;
}
int main() {
test();
system("pause");
return 0;
}
总结:
1、静态成员变量,不属于某个对象上,所有对象共享同一份数据
2、静态成员变量有两种访问方式,通过对象访问,通过类名访问
3、静态成员变量也有访问权限(私有权限无法访问)
示例2:静态成员函数
//静态成员函数
class Person {
public:
static void func() {
s_a = 100;
cout << "静态成员函数访问" << endl;
}
static int s_a;
};
int Person::s_a = 0;
//两种访问方式
void test() {
//访问1:对象访问
Person p;
cout << p.s_a << endl;
p.func();
//访问2:类名访问
Person::func();
cout << p.s_a << endl;
}
int main() {
test();
system("pause");
return 0;
}
总结:
1、静态成员函数的访问方式也有两种:通过对象和通过类名
2、不可以访问非静态成员变量,因为无法区分是哪个对象带来的变量
3、静态成员函数,也有访问权限
四、c++对象模型和this指针
1、成员变量和成员函数分开存储
在c++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上,即静态成员变量和函数不属于类的对象上
空对象所占内存空间是1,每个空对象也有一个独一无二的内存地址
原因是:为了区分空对象占内存的空间位置。
2、this指针概念
this指针 指向 被调用的成员函数 所属的对象(谁调用成员函数指向谁)
概述:
1、this指针式隐含在每一个非静态成员函数内的一种指针
2、this指针不需要定义,可以直接使用
用途:
1、当形参和成员变量同名时,可用this指针来区分
2、在类的非静态成员函数中,返回对象本身,可使用return *this
示例:
//this指针的用途
class Person {
public:
//1、解决名称冲突
//this指针 指向 被调用的成员函数 所属的对象(
void func(int age) {
this->age = age;
}
//2、*this,返回对象本身
Person& addAge(Person& p) {
age += p.age;
return *this;
}
int age;
};
void test1() {
Person p1;
p1.func(10);
cout << "p1年龄:" << p1.age << endl;
Person p2;
p2.func(10);
p2.addAge(p1).addAge(p1).addAge(p1).addAge(p1);
cout << "p2年龄:" << p2.age << endl;
}
void test() {
Person p;
p.func(18);
cout << "年龄:" << p.age << endl;
}
int main() {
//test();
test1();
system("pause");
return 0;
}
3、空指针访问成员函数
C++中,空指针也可以调用成员函数
但需要注意有没有用到this指针,如果用到了this指针,程序会崩溃,需要加以判断条件,保证代码的健壮性
示例:
class Person {
public:
void show() {
cout << "这是一个show函数" << endl;
}
void showage() {
cout << "这是一个showage函数" <<age<< endl;
}
//改进
void showage01() {
if (this == NULL) {
return;
}
cout << "这是一个showage函数" << age << endl;
}
int age;
};
void test() {
//Person p;
Person* p = NULL;
p->show(); //正常调用
//p->showage(); //引发异常
p->showage01(); //调用改进函数showage01()
}
int main() {
test();
system("pause");
return 0;
}
4、const修饰成员函数
常函数:
1、成员函数后加const后我们称为这个函数为常函数
2、常函数内不可以修改成员属性
3、成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
1、声明对象前加const称该对象为常对象
2、常对象只能调用常函数
示例:
注意事项:
1、在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
2、mutable关键字修饰特殊变量,即使是在常函数中,也可以修改
五、友元
作用:让一个函数或者类防问另一个类中私有成员
关键字:friend
友元的三种实现:
1、全局函数做友元
2、类做友元
3、成员函数做友元
1、全局函数做友元
class Bud {
friend void goodGay(Bud* bud);
public:
Bud() {
b_kt = "客厅";
b_ws = "卧室";
}
string b_kt;
private:
string b_ws;
};
//1、全局函数做友元
void goodGay(Bud* bud) {
cout << "好基友访问:" << bud->b_kt << endl;
cout << "好基友访问:" << bud->b_ws << endl;
}
void test() {
Bud bud;
goodGay(&bud);
}
int main() {
test();
system("pause");
return 0;
}
2、类做友元
//建筑类
class Bud {
friend class GoodGay;
public:
Bud() {
b_kt = "客厅";
b_ws = "卧室";
}
string b_kt;
private:
string b_ws;
};
//2、类做友元
//好朋友类
class GoodGay {
public:
void visit(Bud* bud) {
cout << "好基友访问:" << bud->b_kt << endl;
cout << "好基友访问:" << bud->b_ws << endl;
}
};
void test() {
Bud bud;
GoodGay gg;
gg.visit(&bud);
}
int main() {
test();
system("pause");
return 0;
}
3、成员函数做友元
// 3、成员函数做友元
//好朋友类
class goodGay {
public:
goodGay();
//visit1是好基友,可以访问私有属性
void visit();
//visit2不是好基友,不可以访问私有属性
void visit2();
private:
Bud* bud;
};
//建筑类
class Bud {
friend void goodGay::visit();
//friend void goodGay::visit2();
public:
Bud() {
b_kt = "客厅";
b_ws = "卧室";
}
string b_kt;
private:
string b_ws;
};
goodGay::goodGay()
{
bud = new Bud;
}
void goodGay::visit() {
cout << "好基友访问:" << bud->b_kt << endl;
cout << "好基友访问:" << bud->b_ws << endl;
}
void goodGay::visit2() {
cout << "不是好基友访问:" << bud->b_kt << endl;
//cout << "不是好基友访问:" << bud->b_ws << endl;
}
void test() {
Bud bud;
goodGay gg;
gg.visit();
gg.visit2();
}
int main() {
test();
system("pause");
return 0;
}
此类方式,语法要求很严格,不推荐使用!!!