类和对象
1. 概述
1.1 对象
真实存在的事物
1.2 类
多个对象抽取其共同特点形成的概念静态特征提取出来的概念称为成员变量,又名属性
动态特征提取出来的概念称为成员函数,又名方法
1.3 类与对象的关系
在代码中先有类后有对象
一个类可以有多个对象
多个对象可以属于同一个类
2. 类的定义
2.1 语法
class 类名
{[访问权限修饰符:]成员变量成员函数
};
2.2 访问权限修饰符
private: 私有的,当前类中可用,默认的protected: 受保护的,当前类或子类中可用public: 公共的,当前项目中可用
2.3 示例
class Person{
private:int age;
protected:char sex[10];
public:char name[50];void eat(){cout << name << "吃饭" << endl;} void sleep();
};void Person::sleep()
{ cout << name << "睡觉" << endl;
}
3. 封装性
概念:即包装,将数据和方法封装在一起,加以权限区分使其可以保护内部,降低耦合度,便于使用
int a = 10;
int nums[5] = {1,2,3,4,5};
void fun()
{xxx
}
class A
{属性函数
}
A a;
x.c文件
优点:
- 降低代码耦合度
- 提高代码复用率
- 便于使用
4. 类的设计
① 私有化所有属性
② 提供可以获取这些属性值与修改属性值的函数
示例:
#include <iostream>
#include <cstring>using namespace std;class Dog{
private:char name[30];int age;
public:void set_name(char *n){strcpy(name, n);}void set_age(int a){age = a;}char *get_name(){return name;}int get_age(){return age;}void look_dor(){cout << name << "看门" << endl;}void eat(){cout << name << "吃" << endl;}
};int main(int argc, char *argv[])
{Dog d1;d1.set_name("大黄");d1.set_age(2);char *name = d1.get_name();int age = d1.get_age();cout << "姓名:" << name << " 年龄:" << age<< endl;d1.look_dor();Dog d2;d2.set_name("旺财");d2.set_age(1);char *name2 = d2.get_name();int age2 = d2.get_age();cout << "姓名:" << name2 << " 年龄:" << age2 << endl;d2.eat();return 0;
}
//姓名:大黄 年龄:2
//大黄看门
//姓名:旺财 年龄:1
//旺财吃
4.1 练习
请设计一个 Person 类,Person 类具有 name 和 age 属性,提供初始化函数(Init)
并提供对 name 和 age 的读写函数(set,get)
但必须确保 age 的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值
并提供方法输出姓名和年龄
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
private:char name[50];int age;
public:void init(char *n,int a){strcpy(name,n);if(a < 0 || a > 100){age = 0;return;}age = a;}void set_name(char *n){strcpy(name,n);}char* get_name(){return name;}void set_age(int a){if(a < 0 || a > 100){age = 0;return;}age = a;}void print_info(){cout << name << endl << age << endl;}
};
int main(int argc, char *argv[])
{Person p;
// strcpy(p.name,"德玛");
// p.age = -18;p.set_name("德玛");p.set_age(-18);p.print_info();Person p2;p2.init("光头强",18);p2.print_info();return 0;
}
//德玛
//0
//光头强
//18
4.2 练习2:分文件
设计立方体类(Cube)
其属性有长,宽,高
其函数有获取长宽高的函数,修改长宽高的函数
计算体积的函数
计算面积的函数提示:立方体的面积:2ab + 2ac + 2bc体积:a * b * c
main.cpp
文件:
#include <iostream>
#include "cude.h"
using namespace std;int main(int argc, char *argv[])
{Cube c1;c1.set_h(10);c1.set_l(1);c1.set_w(2);c1.mj();c1.tj();return 0;
}
cube.cpp
文件
#include "cude.h"
#include <iostream>
using namespace std;
int Cube::get_l()
{return l;
}
int Cube::get_w()
{return w;
}
int Cube::get_h()
{return h;
}void Cube::set_l(int length)
{l = length;
}void Cube::set_w(int width)
{w = width;
}
void Cube::set_h(int height)
{h = height;
}void Cube::mj()
{int x = 2 * l * w + 2 * l * h + 2 * w * h;cout << "立方体面积为:" << x << endl;
}
void Cube::tj()
{int x = l * w * h;cout << "立方体体积为:" << x << endl;
}
cube.h
文件
#ifndef CUDE_H
#define CUDE_H
class Cube
{
private:int l;int w;int h;
public:int get_l();int get_w();int get_h();void set_l(int length);void set_w(int width);void set_h(int height);void mj();void tj();
};
#endif // CUDE_H
5. 构造函数
5.1 概述
构造函数:类实例化对象的时候自动调用。
注意:
- 当一个类中
没有构造函数
,系统将默认
为其生成一个无参构造
- 如果一个类中有构造函数,系统将不会为其提供默认的无参构造
一个类
可以定义多个
构造函数,该类中的多个构造函数为重载关系
无参构造:
- 构造函数无形参列表
有参构造:
- 构造函数有形参列表
5.2 语法
类名(形参列表)
{ 该类对象赋初始值
} 注意:形参列表可有可无
5.3 示例
#include <iostream>using namespace std;
class A{
private:int a;
public:A(){cout << "无参构造被调用了" << endl;}A(int x){cout << "有参构造被调用了" << endl;}void test(){cout << "test" << endl;}
};int main(int argc, char *argv[])
{//隐式调用无参构造//类名 对象名A a1;//显式调用无参构造//类名 对象名 = 构造函数名();A a2 = A();//隐式调用有参构造A a3(10);//显式调用有参构造A a4 = A(20);//隐式转换(了解)//当调用的构造函数中只有一个参数时可用A a5 = 10;cout << "********************" << endl;//匿名对象//没有对象名的对象// 注意:一个匿名对象只能使用一次A().test();A(1000).test();return 0;
}
//无参构造被调用了
//无参构造被调用了
//有参构造被调用了
//有参构造被调用了
//有参构造被调用了
//********************
//无参构造被调用了
//test
//有参构造被调用了
//test
6. 析构函数
6.1 概述
对象生命周期结束的时候,自动调用析构函数。
注意:
一个类
只能有一个析构函数
- 如果
用户不提供
析构函数 编译器默认会提供一个空的析构函数
。经验:
一般不需要
自定义析构函数,但是如果类中有指针成员且指向堆区空间,这时必须实现析构函数
,在其中释放
指针成员指向的堆区空间
6.2 语法
~类名()
{}
注意:没有形参列表
6.3 示例
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Data{
private:int a;int b;char *msg;
public:Data(){msg = NULL;}Data(int x, int y){a = x;b = y;msg = NULL;}Data(int x, int y, char *m){a = x;b = y;msg = (char *)malloc(50);strcpy(msg, m);}~Data(){cout << "析构函数被调用了" << endl;//判断msg堆区空间是否被释放if(msg != NULL){free(msg);msg = NULL;}}
};void fun01()
{Data d1;
}
int main(int argc, char *argv[])
{//fun01(); //析构函数被调用了 对象生命周期结束自动触发析构函数Data d2; //析构函数被调用了 打开下面的死循环,不会被调用
// while(1);return 0;
}
7. 多对象构造与析构顺序
7.1 对象A与对象B平级,符合栈的顺序(先进后出),谁先创建谁后释放
#include <iostream>using namespace std;
class A{
private:int a;
public:A(){cout << "A无参构造函数被调用了" << endl;}A(int x){a = x;cout << a << "构造函数被调用了" << endl;}~A(){cout << a << "析构函数被调用了" << endl;}
};void fun01()
{A a1(1);A a2(2);{A a3(3);A a4(4);}
}int main(int argc, char *argv[])
{fun01();return 0;
}
结果:
1构造函数被调用了
2构造函数被调用了
3构造函数被调用了
4构造函数被调用了
4析构函数被调用了
3析构函数被调用了
2析构函数被调用了
1析构函数被调用了
7.2 对象A是对象B的成员,先成员构造,再对象构造,再对象析构,再成员析构
#include <iostream>using namespace std;
class A{
private:int a;
public:A(){cout << "A无参构造函数被调用了" << endl;}A(int x){a = x;cout << a << "构造函数被调用了" << endl;}~A(){cout << a << "析构函数被调用了" << endl;}
};void fun01()
{A a1(1);A a2(2);{A a3(3);A a4(4);}
}class B{
private:int b;A a;
public:B(int x){b = x;cout << b << "B的构造函数被调用了" << endl;}~B(){cout << b << "B的析构函数被调用了" << endl;}
};
void fun02()
{B b(5);
}int main(int argc, char *argv[])
{//fun01();fun02();return 0;
}
//A无参构造函数被调用了
//5B的构造函数被调用了
//5B的析构函数被调用了
//4200699构造函数被调用了
8. 拷贝构造函数
8.1 概述
拷贝构造在以下情况自动触发:
- 旧对象给新对象初始化,会调用拷贝构造函数
- 对象作为函数的形参,函数调用时会调用拷贝构造
- 普通对象作为函数的返回值(vsCode会触发拷贝构造,Qt、Linux不会触发拷贝构造)
注意:
如果
用户不提供拷贝构造
编译器会提供一个默认
的拷贝构造(浅拷贝)。只有 类中有指针成员且指向堆区时 才有必要实现拷贝构造(深拷贝)。
浅拷贝与深拷贝:
- 浅拷贝:当类中的成员有指针成员,此时只拷贝地址
- 深拷贝:当类中的成员有指针成员,先开辟内存,再拷贝其值
8.2 语法
类名(const 类名 &ob)
{ }
8.3 示例
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Stu{
public:int x;
public:Stu(){cout << "Stu无参" << endl;}Stu(int a){x = a;cout << "Stu有参" << endl;}/*拷贝构造语法:类名(const 类名& 对象名){函数体}拷贝构造调用情况:1,使用老对象初始化新对象2,调用的函数时,该函数的形参为对象3,函数的返回值是对象*/Stu(const Stu& s){cout << "拷贝构造被调用了" << endl;x = s.x;}
};void fun01()
{Stu s1(10);//将老对象初始化新对象,会触发拷贝构造Stu s2 = s1;cout << "s1.x = " << s1.x << endl;cout << "s2.x = " << s2.x << endl;s2.x = 100;cout << "s1.x = " << s1.x << endl;cout << "s2.x = " << s2.x << endl;
}void test01(Stu s)
{}
void fun02()
{Stu s;test01(s);
}Stu test02()
{static Stu s;return s;
}
void fun03()
{Stu s = test02();
}int main(int argc, char *argv[])
{fun01(); //情况1fun02(); //情况2cout << "--------------" << endl;fun03(); //情况3return 0;
}//Stu有参
//拷贝构造被调用了
//s1.x = 10
//s2.x = 10
//s1.x = 10
//s2.x = 100//Stu无参
//拷贝构造被调用了
//--------------
//Stu无参
//拷贝构造被调用了
8.4 示例:浅拷贝
注意:
- 在释放内存时,先释放p2 ,其指向的地址会被释放,但是p1的地址还是0x01,再次释放就会报错,所以浅拷贝会存在问题
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Person{
private:int age;char *name;
public:Person(int a, char *n){age = a;name = (char *)malloc(50);strcpy(name, n);}//浅拷贝//系统提供的拷贝构造就是浅拷贝//只拷贝地址,不拷贝值Person(const Person& p){name = p.name;age = p.age;}void set_name(char *na){strcpy(name, na);}void print_info(){cout << "姓名:" << name << "\t年龄:" << age << endl;}~Person(){if(name != NULL){free(name);name = NULL;}}
};int main(int argc, char *argv[])
{Person p1 = Person(18, "张三");Person p2 = p1;p1.print_info(); //姓名:张三 年龄:18p2.print_info(); //姓名:张三 年龄:18p2.set_name("李四");p1.print_info(); //姓名:李四 年龄:18p2.print_info(); //姓名:李四 年龄:18return 0;
}
8.5 示例:深拷贝
深拷贝会在堆区再开辟一块内存,将值拷贝到这块内存,即p1和 p2指向了不同的内存空间,释放时是各自释放各自的,不会存在问题
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
class Person{
private:int age;char *name;
public:Person(int a, char *n){age = a;name = (char *)malloc(50);strcpy(name, n);}//深拷贝Person(const Person& p){age = p.age;name = (char *)calloc(1,50);strcpy(name, p.name);}void set_name(char *na){strcpy(name, na);}void print_info(){cout << "姓名:" << name << "\t年龄:" << age << endl;}~Person(){if(name != NULL){free(name);name = NULL;}}
};int main(int argc, char *argv[])
{Person p1 = Person(18, "张三");Person p2 = p1;p1.print_info(); //姓名:张三 年龄:18p2.print_info(); //姓名:张三 年龄:18p2.set_name("李四");p1.print_info(); //姓名:张三 年龄:18p2.print_info(); //姓名:李四 年龄:18return 0;
}
9. 初始化列表
9.1 概述
构造函数:主要用于创建类的对象是给其属性赋初始值
在定义构造函数时,c++中提供了初始化列表的语法,以便于初始化成员变量的值。
9.2 语法
类名(参数列表):成员名(参数名),成员名2(参数名2),... {
}
9.3 示例
#include <iostream>using namespace std;
class A{
private:int a,b;
public:A(){}
// A(int x,int y)
// {
// a = x;
// b = y;
// }A(int x,int y):a(x),b(y){}A(const A& obj){a = obj.a;b = obj.b;}~A(){}void print_info(){cout << a << "\t" << b << endl;}
};class B{
public:int c;A a;
public:B(int z,int x,int y):c(z),a(A(x,y)){}
};
int main(int argc, char *argv[])
{A a(1,7); //1 7a.print_info();B b(1,2,3);cout << b.c << endl; //1b.a.print_info(); //2 3return 0;
}
10. explicit关键字
作用:禁止隐式转换
语法:
explicit 类名(形参列表):初始化列表
{}
示例:
class C{
private:int a;
public:explicit C(int x):a(x){}
};
int main(int argc, char *argv[])
{ C c = 10;//构造函数的隐式转换//当调用构造函数使用explicit修饰后防止隐式转换,此时上述代码报错return 0;
}
11. new / delete
-
当用
new
创建一个对象时,它就 在堆里为对象分配内存并调用构造函数完成初始化; -
new 表达式的反面是 delete 表达式。
delete
表达式先调用析构函数,然后释放内存。正如 new 表达式返回一个指向对象的指针一样, delete 需要一个对象的地址。delete 只适用于由 new 创建的对象。
11.1 情况1:操作基本类型
作用:
- new:申请堆区空间;
- delete:释放堆区空间。
示例:
#include <iostream>using namespace std;void fun01()
{int *p1 = new int;*p1 = 100;cout << *p1 << endl; //100int *p2 = new int(200); //给p2的值初始化为200cout << *p2 << endl; //200delete p1;delete p2;
}int main(int argc, char *argv[])
{fun01();return 0;
}
11.2 情况2:操作数组
作用:
- new:申请堆区空间;
- delete:释放堆区空间。
示例:
#include <iostream>
using namespace std;
void fun02()
{//int nums01[5] = {0};int *nums01 = new int[5];//int nums02[5] = {1,2,3,4,5};int *nums02 = new int[5]{1,2,3,4,5};for(int i = 0; i < 5; i++){cout << nums01[i] << endl;}cout << "------------------" << endl;for(int i = 0; i < 5; i++){cout << nums02[i] << endl;}//delete nums01;//只会释放数组中第一个元素的内存delete [] nums01;delete [] nums02;
}int main(int argc, char *argv[])
{fun02();return 0;
}
//7798976
//7798976
//0
//0
//-150994442
//------------------
//1
//2
//3
//4
//5
11.3 情况3:操作对象
作用:
- new:分配空间,调用构造函数 ;
- delete:调用析构函数,释放空间 。
示例:
#include <iostream>using namespace std;class A{
public:A(){cout << "A无参" << endl;}~A(){cout << "A析构" << endl;}
};
int main(int argc, char *argv[])
{//不会调用构造函数
// A *a = (A *)calloc(1,sizeof(A));//不会调用析构函数
// free(a);//会调用构造函数A *a1 = new A();//会调用析构函数delete a1;
// while(1);return 0;
}
12. 对象数组
数组中存放的是对象。
对象数组:必须 显示
调用构造函数初始化
12.1 静态对象数组
示例:
#include <iostream>using namespace std;
class A{
public:int mA;
public:A(){cout<<"A无参构造"<<endl;}A(int a){mA = a;cout<<"A有参构造mA="<<mA<<endl;}~A(){cout<<"A析构函数mA="<<mA<<endl;}
};
void fun04()
{//对象数组 必须显示调用构造函数初始化A arr[5]={A(10),A(20),A(30),A(40),A(50)};int n = sizeof(arr)/sizeof(arr[0]);int i=0;for(i=0;i<n;i++){cout<<arr[i].mA<<" ";}cout<<endl;
}int main(int argc, char *argv[])
{fun04();return 0;
}
//A有参构造mA=10
//A有参构造mA=20
//A有参构造mA=30
//A有参构造mA=40
//A有参构造mA=50
//10 20 30 40 50
//A析构函数mA=50
//A析构函数mA=40
//A析构函数mA=30
//A析构函数mA=20
//A析构函数mA=10
12.2 动态对象数组
示例:
void fun05()
{ A *arr = new A[5]{A(10),A(20),A(30),A(40),A(50)};int i=0;for(i=0;i<5;i++){cout<<arr[i].mA<<" ";} cout<<endl;//delete arr;//只会释放第0个元素delete [] arr;
}//A有参构造mA=10
//A有参构造mA=20
//A有参构造mA=30
//A有参构造mA=40
//A有参构造mA=50
//10 20 30 40 50
//A析构函数mA=50
//A析构函数mA=40
//A析构函数mA=30
//A析构函数mA=20
//A析构函数mA=10
13. 静态成员
static 修饰的成员为静态成员。
建议使用 类名 调用静态成员
- 静态成员在类加载之初被加载,不占用对象的空间
- 非静态成员加载在对象中
- 成员函数加载到代码区
13.1 静态成员变量
13.1.1 概述
特点:
- 静态成员是
属于类
而不是对象。(所有对象共享)注意:
- 静态成员数据
不占对象的内存空间
。- 静态成员数据是属于类 而不是对象(多个对象共享一份静态成员数据)
- 静态成员数据 在
定义对象之前
就存在。静态成员数据在类中定义,类外初始化。
13.1.2 示例
#include <iostream>
using namespace std;
class A{
public:static int num;int num03;
public:A(){}
};
//类外初始化
int A::num = 10;
int main(int argc, char *argv[])
{A a1 = A();a1.num03 = 20;cout << sizeof(a1) << endl; //4cout << a1.num << endl; //10//对象a1.num03赋值cout << "a1.num03 = " << a1.num03 << endl;//a1.num03 = 20A a2;//对象a2.num03没有赋值,所以是随机值cout << "a2.num03 = " << a2.num03 << endl;//a2.num03 = 6422384cout << "a1.num = " << a1.num << endl;//a1.num = 10cout << "a2.num = " << a2.num << endl;//a2.num = 10//通过a2修改num的值,a1.num也随之改变a2.num = 100;cout << "a1.num = " << a1.num << endl;//a1.num = 100cout << "a2.num = " << a2.num << endl;//a2.num = 100return 0;
}

13.1.3 小结
1,属于类的,不属于对象,当前类中所有对象共有一份,不占用对象的内存空间
2,可以使用类名直接调用,也可以使用对象名调用类名调用:类名::静态成员变量名对象调用:对象名.静态成员变量名
3,在类中定义,在类外初始化,初始化时不关注访问权限修饰符,在类外使用时关注访问权限修饰符
13.2 静态成员函数
使用 static 修饰的成员函数
特点:
- 静态成员函数中
只能使用静态成员
。
示例
#include <iostream>
#include "cstring"
using namespace std;
class Stu{
private:static char c[50];char name[50];int age;
public://有参构造函数,方便对象初始化Stu(char n[50],int a){strcpy(name,n);age = a;}void print_info(){cout << name << endl;cout << age << endl;cout << c << endl;}//注意静态成员函数中只能使用静态成员static void test(){//cout << name << endl; //报错//cout << age << endl;cout << c << endl;}
};
//当静态为私有的
//此时可以对其进行初始化,但是不能直接使用
char Stu::c[50] = "iot2302";int main(int argc, char *argv[])
{//隐式调用有参构造Stu s01("张三",23);Stu s02("李四",24);Stu s03("王五",23);s01.print_info();s02.print_info();s03.print_info();//使用对象名调用静态成员(public权限下)//strcpy(s01.c,"iot2303");//建议使用类名调用静态成员(public权限下)//cout << Stu::c << endl;s01.print_info();s02.print_info();s03.print_info();//静态成员函数,可以使用类名调用,也可以使用对象名调用s01.test();Stu::test();return 0;
}//张三
//23
//iot2302
//李四
//24
//iot2302
//王五
//23
//iot2302//张三
//23
//iot2302
//李四
//24
//iot2302
//王五
//23
//iot2302//iot2302
//iot2302
14. 单例模式
设计模式:
每一个设计模式是为了解决 一种特点的问题
概述:所属的类 只能实例化一个对象。
思想:
- 私有化构造函数
- 提供公共函数,返回该类对象
14.1 饿汉式:返回创建好的对象
缺点:占用内存多
优点:线程安全
示例:
#include <iostream>using namespace std;
class A{
private:int x;//实例化A的对象,此时是个指针static A *a;
private:A(){}A(const A& a){}
public://普通函数在外部只能通过对象调用,这个类在外部没法创建对象,//所以只能创建为公共静态函数,在外部通过类调用。//这个函数是为了返回该类的对象,如果只返回A,还是会触发拷贝构造(旧对象给新对象初始化),//因为拷贝构造函数已经私有化,所以只能返回类的引用static A& getInstance(){//返回该类的对象(对a取值,就是对象)return *a;}
};
// 静态成员变量 类外初始化,开辟内存,所以是地址,用指针接收
A *A::a = new A();
void fun01()
{//返回的是A的引用个,所以接收的也是A的引用这种类型A& a1 = A::getInstance();A& a2 = A::getInstance();A& a3 = A::getInstance();//打印这3个对象的地址,相同说明实例化的是同一个对象cout << &a1 << endl; //0x1e7030cout << &a2 << endl; //0x1e7030cout << &a3 << endl; //0x1e7030
}int main(int argc, char *argv[])
{fun01();return 0;
}
14.2 懒汉式:第一次调用时创建对象并返回,后面调用返回第一次创建的对象
缺点:线程不安全
优点:占内存少
示例:
class B{
private:int x;static B* b;
private:B(){}B(const B& b){}
public:static B& getInstance(){//判断是否已经创建对象if(b == NULL){b = new B();}return *b;}
};
B *B::b = NULL;
int main(int argc, char *argv[])
{B& b1 = B::getInstance();B& b2 = B::getInstance();B& b3 = B::getInstance();cout << &b1 << endl; //0xf37040cout << &b2 << endl; //0xf37040cout << &b3 << endl; //0xf37040return 0;
}