网站开发技术交流,花灯彩灯制作公司,wordpress百度收录之自动推送设置,网站建设电话销售1 概述
C是面向对象的语言#xff0c;面向对象语言三大特性#xff1a;封装、继承、多态。 C将万事万物抽象为对象#xff0c;对象上有其属性和行为。
2 封装
2.1 封装的意义
封装是面向对象的三大特性之一#xff0c;封装将属性和行为作为一个整体#xff0c;对属性和…1 概述
C是面向对象的语言面向对象语言三大特性封装、继承、多态。 C将万事万物抽象为对象对象上有其属性和行为。
2 封装
2.1 封装的意义
封装是面向对象的三大特性之一封装将属性和行为作为一个整体对属性和行为加以权限控制。 创建类语法
class 类名 {
访问权限:属性;行为;
};示例
class Student {
private:string m_name;int m_age;
public:Student(string name, int age) {m_name name;m_age age;}string getName() {return m_name;}
};上面除了定义属性和行为也定义了权限访问控制符 C包含三种访问控制符
public 可被类中函数、子类函数、友元函数和类对象访问protected 可被类中函数、子类函数、友元函数访问private 可被类中函数和友元函数访问
2.2 struct和class区别
二者的区别在于默认的访问权限不同struct默认访问权限为publicclass默认访问权限为private
#include iostream
using namespace std;class C1 {int m_A;
};struct S1 {int m_A;
};int main() {C1 c1;//c1.m_A; 报错无法访问S1 s1;s1.m_A 10;return 0;
}2.3 成员属性设置为私有
通常将成员属性设置为私有并提供设置和获取的方法。 这样的好处是可以控制成员属性的访问权限对于写权限能够检测数据的有效性。
class Test {
public://num1提供读写接口void setNum1(int num1) {m_num1 num1;}int getNum1() {return m_num1;}//num2只提供读接口int getNum2() {return m_num2;}//num3只提供写接口并判定数据范围void setNum3(int num3) {if (num3 0) {m_num3 num3;} else {m_num3 0;}}private:int m_num1;int m_num2;int m_num3;
};封装的思想通常会将属性设置为私有暴露必要的修改行为给外部。
3 对象创建和清理
3.1 构造函数和析构函数
一个类具有最基础的两个函数是构造函数和析构函数即使程序员未添加这两个函数编译器也会生成一个默认的版本。 其中构造函数用于为类创建一个对象并进行一些初始化的工作。 析构函数与构造函数相反是为了清理一个对象并释放资源。 构造函数和析构函数都是由编译器自动调用。
class Test {
private:int m_num;
public:Test();Test(int num) {m_num num;}~Test();
};构造函数可以重载而析构函数只能有一个。构造函数可以通过传入不同的参数来重载而析构函数没有参数。 当未定义构造函数和析构函数时编译器会自动生成以下两个实现
Test(){}
~Test(){}默认会生成两个空实现。 而如果提供了有参构造函数则编译器不会提供默认的空参构造函数如果有此使用场景则需要自己再提供一个空参构造函数。
3.2 构造函数分类及调用方式
构造函数有两种分类方式 按参数分为有参构造和无参构造 按类型分类普通构造和拷贝构造 三种调用方式 括号法 显示法 隐藏转换法
#include iostreamusing namespace std;class Person {
public://无参构造函数Person() {cout 无参构造函数 endl;}//有参构造函数Person(int age) {cout 有参构造函数 endl;m_age age;}//拷贝构造函数Person(const Person person) {cout 拷贝构造函数 endl;m_age person.m_age;}~Person() {cout 析构函数 endl;}public:int m_age;
};void test1() {//调用无参构造函数Person p1;//显示调用无参构造函数Person p2 Person();//调用拷贝构造函数Person P3(p1);
}void test2() {//括号法Person p1(10);//括号不能用于调用无参构造函数这样定义会被认为是一个函数声明//Person p();//显示法Person p2 Person(10);Person p3 Person(p2);//创建匿名对象无法使用直接析构Person();Person(10);//隐式转换法隐式调用有参构造函数和拷贝构造函数Person p4 10;Person p5 p4;//不能利用拷贝构造初始化匿名对象会被认为是对象声明//Person p6(p4);
}int main() {test1();test2();
}输出
无参构造函数
无参构造函数
拷贝构造函数
析构函数
析构函数
析构函数
有参构造函数
有参构造函数
拷贝构造函数
无参构造函数
析构函数
有参构造函数
析构函数
有参构造函数
拷贝构造函数
析构函数
析构函数
析构函数
析构函数
析构函数比较需要注意的是隐藏转换法
3.3 拷贝构造函数的调用时机
调用拷贝构造函数有三种情况
使用一个已有的对象去初始化另一个对象值传递的方式给对象类型参数传值以值的方式返回局部对象
#include iostreamusing namespace std;class Person {
public://无参构造函数Person() {cout 无参构造函数 endl;}//有参构造函数Person(int age) {cout 有参构造函数 endl;m_age age;}//拷贝构造函数Person(const Person person) {cout 拷贝构造函数 endl;m_age person.m_age;}~Person() {cout 析构函数 endl;}public:int m_age;
};void test1() {//调用有参构造函数Person p1(10);//括号法调用拷贝构造函数Person p2(p1);//隐式转换法调用拷贝构造函数Person p3 p1;//显示调用拷贝构造函数Person p4 Person(p1);//赋值操作不会调用拷贝构造函数Person p5;p5 p1;
}void doSomething1(Person p){}
void test2() {Person p;//对象赋值给形参调用拷贝构造函数doSomething1(p);
}Person doSomething2() {Person p;return p;
}
void test3() {Person p doSomething2();
}int main() {test1();test2();test3();
}输出
有参构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
无参构造函数
析构函数
析构函数
析构函数
析构函数
析构函数
无参构造函数
拷贝构造函数
析构函数
析构函数
无参构造函数
析构函数最后返回局部对象与预想中有差异是因为编译器优化感兴趣可以搜索RVO了解。 由于返回值和值传递传对象都会导致对象复制对象大小会随着属性的增加而增加所以一般对象作为参数的时候都是使用引用或者指针进行传参或返回。
3.4 构造函数调用规则
默认情况下C编译器会至少给一个类添加3个函数
默认构造函数无参构造函数实现为空默认析构函数空实现析构函数默认拷贝构造函数对定义的属性进行拷贝
构造函数生成规则如下
如果用户定义有参构造函数则编译器不会提供默认无参构造函数但还是会提供默认拷贝构造函数如果用户定义拷贝构造函数则编译器不会提供默认无参构造函数
1、使用编译器默认函数
class Person {
public:int m_age;
};int main() {Person p1;p1.m_age 10;Person p2 p1;cout p2.m_age p2.m_age endl;
}这里调用了默认无参构造和拷贝构造函数。析构函数由于没有实现所以无法体现。
2、提供有参构造的情况
class Person {
public://无参构造函数//Person() {// cout 无参构造函数 endl;//}//有参构造函数Person(int age) {cout 有参构造函数 endl;m_age age;}//拷贝构造函数Person(const Person person) {cout 拷贝构造函数 endl;m_age person.m_age;}~Person() {cout 析构函数 endl;}public:int m_age;
};没有匹配的构造函数调用编译器不提供默认构造函数。 3、提供拷贝构造函数
class Person {
public://拷贝构造函数Person(const Person person) {cout 拷贝构造函数 endl;m_age person.m_age;}~Person() {cout 析构函数 endl;}public:int m_age;
};提供了拷贝构造函数后不会提供默认无参构造函数。
3.5 深拷贝和浅拷贝
浅拷贝简单的赋值拷贝指向同一个内存区域 深拷贝在堆中重新申请空间进行拷贝
class Person {
public://无参默认构造函数Person() {cout 无参构造函数! endl;}//有参构造函数Person(int age ,int height) {cout 有参构造函数! endl;m_age age;m_height new int(height);}//拷贝构造函数 Person(const Person p) {cout 拷贝构造函数! endl;//如果不利用深拷贝在堆区创建新内存会导致浅拷贝带来的重复释放堆区问题m_age p.m_age;m_height new int(*p.m_height);}//析构函数~Person() {cout 析构函数! endl;if (m_height ! NULL){delete m_height;}}
public:int m_age;int* m_height;
};void test01()
{Person p1(18, 180);Person p2(p1);cout p1的年龄 p1.m_age 身高 *p1.m_height endl;cout p2的年龄 p2.m_age 身高 *p2.m_height endl;
}int main() {test01();return 0;
}由于类中的属性是在堆上分配的如果使用浅拷贝会导致两个对象的m_height都指向了同一个堆内存区域而当一个对象析构之后会释放该内存区域而另一个对象还在使用该内存区域会导致不确定的结果。而第二个对象析构时则会导致重复释放堆区会导致程序终止。所以如果有堆上分配内存的属性需要使用深拷贝重新申请堆内存区域进行赋值。 示例如下
void test1() {Person p1(18, 180);{Person p2(p1);cout p2.height *p2.m_height endl;}cout p1.height *p1.m_height endl;
}int main() {test1();
}输出
有参构造函数
p2.height 180
析构函数
p1.height 887973408
析构函数Process finished with exit code -1073740940 (0xC0000374)上面是注释了深拷贝的结果p2正常访问然后p2被析构此时p1的m_height指向的内存实际已经释放所以访问该内存会导致不确定的结果然后p1会重复释放m_height指向的内存导致程序出错终止。
3.6 初始化列表
C提供了一种初始化列表的语法来初始化属性
class Person {
public:传统方式初始化//Person(int a, int b, int c) {// m_A a;// m_B b;// m_C c;//}//初始化列表方式初始化Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}void PrintPerson() {cout mA: m_A endl;cout mB: m_B endl;cout mC: m_C endl;}
private:int m_A;int m_B;int m_C;
};int main() {Person p(1, 2, 3);p.PrintPerson();return 0;
}初始化列表的语法可以省略原始构造函数中的一些模版代码。
3.7 类对象作为类成员
class Phone
{
public:Phone(string name){m_PhoneName name;cout Phone构造 endl;}~Phone(){cout Phone析构 endl;}string m_PhoneName;
};class Person
{
public://初始化列表可以告诉编译器调用哪一个构造函数Person(string name, string pName) :m_Name(name), m_Phone(pName){cout Person构造 endl;}~Person(){cout Person析构 endl;}void playGame(){cout m_Name 使用 m_Phone.m_PhoneName 牌手机! endl;}string m_Name;Phone m_Phone;};
void test01()
{//当类中成员是其他类对象时我们称该成员为 对象成员//构造的顺序是 先调用对象成员的构造再调用本类构造//析构顺序与构造相反Person p(张三 , 苹果X);p.playGame();}int main() {test01();
}先调用对象成员的构造函数然后调用本类的构造函数。析构的时候正好与构造的时候顺序相反。
3.8 静态成员
静态成员就是在成员变量或者成员函数前加上static关键字
静态成员变量 所有对象共享同一份数据在编译阶段分配内存类内声明类外初始化 静态成员函数 所有对象共享同一个函数静态成员函数只能访问静态成员变量 1、静态成员变量
class Person
{public:static int m_A; //静态成员变量
private:static int m_B; //静态成员变量也是有访问权限的
};
//类外初始化
int Person::m_A 10;
int Person::m_B 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.m_A 100;cout p1.m_A p1.m_A endl;Person p2;p2.m_A 200;cout p1.m_A p1.m_A endl; //共享同一份数据cout p2.m_A p2.m_A endl;//2、通过类名cout m_A Person::m_A endl;//cout m_B Person::m_B endl; //私有权限访问不到
}int main() {test01();
}静态成员变量也有访问权限与非静态成员变量的访问权限一样。静态成员变量可以通过对象和类名访问。
class Person
{
public:static void func(){cout func调用 endl;m_A 100;//m_B 100; //错误不可以访问非静态成员变量}static int m_A; //静态成员变量int m_B; //
private://静态成员函数也是有访问权限的static void func2(){cout func2调用 endl;}
};
int Person::m_A 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.func();//2、通过类名Person::func();//Person::func2(); //私有权限访问不到
}int main() {test01();
}静态成员函数只能访问静态成员变量也只能直接调用静态成员函数可以在其中创建对象来调用非静态成员函数因为非静态成员函数属于对象。静态成员函数也可以通过对象和类名两种方式调用。