嵌入式开发学习 C++:day02
类和对象
C++面向对象的三大特性为:封装性、继承性、多态性。
C++面向对象的三大特征为:封装、集成、多态。
C++认为万事万物都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重……,行为走、跑、吃饭、睡觉……
车也可以作为对象,属性有轮胎、方向盘、车灯、发动机……,行为有载人、放音乐、开空调……
具有相同特征的对象,我们可以抽象称为,人属性人类,车属于车类。
什么是对象(Object)
现实角度:对象是现实中的事物。
对象的组成:
- 静态特征
- 动态行为
编程角度:某个类型的变量
什么是类(Class)
现实角度:类似对象的抽象描述。
类的组成:
- 成员数据(属性)
- 成员方法(行为)
编程角度:某个变量的类型
类与对象的关系
现实角度:
- 类是对象的抽象描述(类是对象的模版)
- 对象是类的实例
typedef struct
{int id ;// 结构体成员char *name;int age;
}Student;// 结构体变量 -> 结构体实例
Student xiaoliu = {0};
编程角度:
- 类是对象的类型
- 对象是类的变量
类的定义
类的组成:
数据成员→属性→静态特征
成员函数→方法→动态行为
流程控制三大结构:循环结构、分支结构、顺序结构
定义格式:
class 类名
{成员的访问属性:数据成员;成员函数;成员的访问属性:数据成员;成员函数;...
};
对象的实例化
对象的实例化过程就是为类分配内存空间的过程。
分配空间的大小往往取决于类中数据成员,系统不会为对象申请存储成员函数的空间的。
对象的实例化方法:
借助类名实例化对象:
类 对象列表; // 数据类型 变量列表;
例子:
class Student
{....
};// 借助类名实例化对象,其实就是利用Student这个数据类型创建三个变量
Student zhangsan,lisi,wangwu;
类定义的同时实例化对象:
class Student
{...
}zhangsan,lisi,wangwu;
动态实例化对象:(new 运输符/delete 运算符)
new 运算符使用方法:目标类型 *p = new 目标类型;
例子:
class Student
{...
};// 给张三在堆区分配内存
Student *zhangsan = new Student; // 类似于 Student *zhangsan = (Student)malloc(sizeof(Student));
delete zhangsan; // 释放单一对象 ,delete 对象名;int *p = new int [67]; // 给int类型数组分配内存,类似于 int *p = (int*)malloc(67 * sizeof(int));
delete []p; // 等价于delete[]p ; 释放数组,delete[]数组名;
对象成员的访问
对象成员访问要借助类对象或者对象指针。
借助于类对象:
对象.成员名;
注意成员的访问属性;
借助对象指针:
对象指针 -> 成员名;
注意成员的访问属性;
typedef struct
{int id;string name;int age;
}Student;Student stu = {};stu.id = 12;
stu.name = "张三";
stu.age = 21;Student *p = &stu;
p->id = 13;
p->name = "李四";
p->age = 22;(*p).id = 13;
(*p).name = "lisi";
(*p).age = 22;
类的嵌套
// 结构体嵌套
typedef struct
{int id;string name;int age;struct{int year;int month;int day;} birthday;
}Dog;
C ++是支持类的嵌套定义的:
类的嵌套定义:在一个类的内部又有一个完整的类定义
// 类的嵌套定义
class Dog// 外部类
{...class Birthday // 内部类{...}...
};
类的嵌套相当于给内部类设置了一层作用域。
说明:
- 内部类如果是共有访问属性,在外部类的类外,可以通过
外部类::内部类
访问到内部类; - 内部类如果是私有/保护访问属性,在外部类的类外,想要访问到内部类,只能通过外部类提供内部类的共有对象做数据成员来实现。
命名空间(namespace)
对于一个大型程序,往往会用到很多独立的库,对于不同组织或公司提供的库,对于不同组织或公司提供的库,难免存在不同库之间存在同名变量或同名函数的问题,这样在使用的时候就会发生冲突,为了解决这个问题,引入了命名空间。
命名空间分割了全局命名空间,其中每个命名空间就是一个作用域,这样不同命名空间中的标识符可以同名而不会发生冲突。
Namespace(命名空间)主要用来解决变量或函数重名问题。
命名空间定义
语法:
namespace 命名空间
{变量/函数/类;
}
使用命名空间成员的方法:命名空间:: 命名空间
,举例:std::cin
示例:
/*************************************************************************> File Name: demo01.cPP> Author: 小刘> Description: 命名空间> Created Time: 2025-08-19 10:15:14************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;/*** 自定义命名空间*/
namespace ASpace
{int count = 100; // 全局变量void add(){cout << "我是ASpace命名空间的add 函数" << endl;}
}int main()
{cout << "程序开始" << endl;cout << ASpace::count << endl; // :: 叫做作用域分辨符/作用域分隔符ASpace::add();system("pause");return 0;
}
在声明一个命名空间时,花括号内不仅可以包括变量常量,还可以包含类型比如类类型或者结构体,模版,甚至是另一个命名空间等。
/*************************************************************************> File Name: demo02.cpp> Author: 小刘> Description: 命名空间> Created Time: 2025-08-19 10:24:42************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;// 自定义一个空间
namespace A
{int count = 100;void add(){cout << "我是A命名空间的add 函数" << endl;}// 自定义嵌套的命名空间namespace AA{string name = "txs"; // 全局变量}
}int main()
{cout << A::count << endl;A::add();cout << A::AA::name << endl; // 使用嵌套的命名空间system("pause");return 0;
}
使用命名空间解决冲突
/*************************************************************************> File Name: demo02.cpp> Author: 小刘> Description: 命名空间> Created Time: 2025-08-19 10:24:42************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;// 自定义一个空间
namespace A
{int count = 100;void add(){cout << "我是A命名空间的add 函数" << endl;}// 自定义嵌套的命名空间namespace AA{string name = "txs"; // 全局变量}
}int main()
{cout << A::count << endl;A::add();cout << A::AA::name << endl; // 使用嵌套的命名空间system("pause");return 0;
}
命名空间起别名
可以为命名空间起一个别名,用来代替比较成的命名空间。
语法:
namespace 别名 = 当前命名空间名;
举例:
/*************************************************************************> File Name: demo02.cpp> Author: 小刘> Description: > Created Time: 2025-08-19 10:24:42************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;// 自定义一个空间
namespace MYspace
{int count = 100;
}// 给已有命名空间取别名
namespace MS = MYspace;int main()
{cout << A::count << endl;A::add();cout << A::AA::name << endl; // 使用嵌套的命名空间system("pause");return 0;
}
using namespace 命名空间
在程序中,可以使用usingnamespace命名空间名
声明一个空间中的全部成员。
在使用该命名空间的任何成员时都不必用命名空间限定。
#include <iostream>
#include <iomanip>using namespace std;namespace MYSplace
{int count = 100;string name = "pk";
}
using namespace MYSplace;
int main()
{//cout << MYSplace::count << MYSplace::name << endl;cout << count << name << endl; // 不声明std,写法,std::count <<count<<name<<endl;return 0;
}
在用using namespace
声明的作用域中,命名空间myspace的成员就像在全局域声明的一样取使用。一般命名空间使用较少,如果这些命名空间没有同名成员才可以这样做。
无名命名空间
由于命名空间没有名字,在其他文件中显然无法引用,它只有在本文件的作用域有效。在文件A中使用无名命名空间的成员,不必(也无法)用命名空间名限定,如下列所示:
//在本程序中的其他⽂件中也⽆法使用该fun函数, 也就是把fun函数的作用域限制在本⽂件范围中。
namespace // 声明一个⽆名的命名空间
{int count=100;string name="pk";
}
int main()
{cout << count << name << endl;return 0;
}
标准命名空间
为了解决C++标准库中的标识符与程序中的全局标识符之间以及不同库中的标识符之间的同名冲突。
标准C++库的所有的标识符都是在一个名为std的命名空间中定义的,或者说标准头⽂件(如iostrea
)中函数、类、对象和类模板是在命名空间std中定义的。这样,在程序中用到C++标准库时,需要使用std作为限定。
std::cout<<"wjg"<<endl;
在⼤多数的C++程序中常用using namespace语句对命名空间std进行声明,这样可以不必对每个命名空间成员一一进行处理,在⽂件的开头加⼊以下using namespace
声明
成员函数重载(overload)
**重载:**完成不同功能的函数使用了同一个函数名。
重载条件:
①函数重载时,函数的形参类型不同
②函数重载时,函数的形参个数不同
③例外?? (待解密)
注意: 重载是针对同一作用域下的函数的。 原理: 名字粉碎 (name-mangling
) 查看C++经过编译后的函数名可通过指令nm -s
目标文件
C++编译器为C++中的所有函数,在符号表中生产唯一的标识符来区分不同函数,而对于同名不同参的函数,编译器会结合函数名及参数类型生成唯一标识符,以支持函数重载 。
成员函数的参数缺省
把握原则:
- 成员函数的参数缺省,如果不是完全缺省,则需要从右边缺省起;
- 成员函数的参数缺省,要特别注意对重载函数的影响,要避免产生 二义性问题
(ambiguous)
;
封装
封装是C++面向对象三大特征之一。
封装的意义
- 将属性(数据成员)和行为(成员函数)作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制(比如公共、受保护、私有)。
在C语言中:
- 封装一组不同类型的数据类型,使用结构体
- 封装一组相同类型的数据类型,使用数组
封装意义一
在设计类的时候,属性和行为写在一起,表示事物。
语法:
class 类名
{访问权限:属性/行为..
}
示例1:
需求:设计一个圆类,求圆的周长
代码:
/*************************************************************************> File Name: demo04.cpp> Author: 小刘> Description: > Created Time: 2025-08-19 12:04:38************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;// 定义圆周率(全局变量)
const double PI = 3.14;/*** 1.封装的意义:* 将属性和行为作为一个整体,用来表示生活中的事物* 封装一个圆类,求圆的周长*/
class Circle // 类的一般首字母大写,用于和对象进行区分
{
public: // 访问权限 公共的权限// 属性:数据成员int m_r; //半径// 行为:成员函数 获取圆的周长double calculateZC(){return 2 * PI * m_r;}
};int main()
{// 通过圆类,创建圆对象Circle c1;// 对象1c1.m_r = 5;cout << "圆的周长为: " << c1.calculateZC() << endl;Circle c2;// 对象2c2.m_r = 10;cout << "圆的周长为: " << c2.calculateZC() << endl;system("pause");return 0;
}
示例2:
-
需求:设计一个学生类,属性有姓名学号,可以给姓名学号复制,可以显示学生的姓名和学号。
-
代码
/*************************************************************************> File Name: demo05.cpp> Author: 小刘> Description: 封装> Created Time: 2025-08-19 13:34:58************************************************************************/ #include <iostream> #include <iomanip>using namespace std;/*** 创建一个学生类*/ class Student { public: // 访问权限:公共的void setName(string name){m_name = name;}void setID(int id){m_id = id;}void showStudent(){cout << "name:" << m_name << "id :" << m_id << endl;} public:string m_name; // C++ 支持字符串变量int m_id; };int main() {// 创建一个Stude 类型的变量Student stu;stu.setID(666);stu.setName("小刘");stu.showStudent();system("pause");return 0; }
封装意义二
在设计时可以把属性和行为放在不同权限下,加以控制
访问权限有三种
权限名称 | 关键字 | 类内部 | 类外部 |
---|---|---|---|
公共权限 | public | 类内可以访问 | 类外可以访问 |
保护权限 | protected | 类内可以访问 | 类外不可以访问 |
私有权限 | private | 类内可以访问 | 类外不可以访问 |
说明:成员访问属性关键字可以在类内出现多次,它们的作用范围是从指定处到下一个关键字出现的地方,或者是到类的结束。
类的设计原则及注意事项:
- 类的设计过程中,首先要考虑的是类中的核心数据成员;
- 类中的数据成员尽量私有或者保护,成员函数尽量公开,尽量为私有或保护的数据成员提供公有的成员函数,以便在类外通过公有函数间接访问私有或保护的数据成员。
- 类中成员数据尽量放在一起,成员函数放在一起;
- 成员函数的实现尽量放置在类外,类内仅作成员函数声明
- 类定义时,尽量不要给成员变量赋初值。原因一:这种语法只有在C++11标准后才支持;原因二:定义一个类仅仅是定义类型,系统不会为类中的成员开辟内存,当然数据也就无法存储。
- 类定义时,不要用auto, extern, register存储类型去修饰数据成员,但static除外
示例:
/*************************************************************************> File Name: demo06.cpp> Author: 小刘> Description: > Created Time: 2025-08-19 14:01:36************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;/*** 创建一个Person*/
class Person
{
public : // 公共权限string name; // 属性:名字
protected: // 保护权限double weight; // 属性:体重
private: // 私有权限int age ; // 属性:年龄
public:void func(){name = "张三";weight = 198;age = 21;}
};int main()
{Person person;person.name = "李四"; // 类外部访问,可以访问到 public//person.weight = 166; // 类外部访问,不可以访问到 protected//person.age = 21; // 类外部访问,不可以访问到 privatereturn 0;
}
struct和class 的区别
在C++中struct和class唯一的区别就在于默认的访问权限不同
区别:
struct
默认权限为公共class
默认权限为私有
/*************************************************************************> File Name: demo07.cpp> Author: 小刘> Description: 封装> Created Time: 2025-08-19 15:38:31************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;//类
class C1
{int m_A; // 不加权限访问符,默认是私有
};// 结构体
struct C2
{int m_A; // 默认是公共权限
};int main()
{C1 c1;//C1.m_A;// 错误,访问权限是私有C2 c2; // C++ 中,访问结构体的时候,可以省略struct关键字c2.m_A = 10; // 正确,访问权限是公共cout << c2.m_A << endl;system("pause");return 0;
}
成员属性设置为私有
优点:
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,我们可以检测数据的有效性
示例(推荐写法):
/*************************************************************************> File Name: demo08.cpp> Author: 小刘> Description: > Created Time: 2025-08-19 15:47:28************************************************************************/
#include <iostream>
#include <iomanip>using namespace std;/*** 创建一个Person* 封装:属性私有化,对其提供公共的访问方法*/
class Person
{
public : // 方法共有化// m_name:写void setName(string name){m_name = name;}// m_name :读string getName(){return m_name;}void setAge (int age){m_age = age;}int getAge(){return m_age;}void setaddress(string address){m_address = address;}string getAddress(){return m_address;}private:string m_name; // 姓名 可读可写int m_age ; // 年龄 可读可写string m_address; // 住址 可读可写
};int main()
{Person p;// 姓名设置p.setName("张三");cout << "姓名:" << p.getName() << endl;// 年龄设置p.setAge(21);cout << "年龄: " << p.getAge() << endl;// 住址设置p.setaddress("陕西西安");cout << "住址:" << p.getAddress() << endl;system("pause");return 0;
}
对象的初始化和清理
- 生活中我们买的电子产品基本上都有出厂设置,在某一天我们不用的时候就会删除一些自己信息,保证数据安全。
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题。
一个对象或者对象没有初始状态,对其使用后果时未知的,如 int a ; cout << a << emdl;
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。 int *p ...free (p);
C++ 利用了构造函数和析构函数解决上述问题。这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供构造函数和析构函数,不过此时的构造函数和析构函数的空实现。
- 构造函数:主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要用作在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法: 类名() {}
①构造函数,没有返回值也不写void
②函数名称与类相同
③构造函数可以有参数,因此可以发生重载
函数的重载:同一作用域,函数名必须相同,参数个数、参数类型、参数顺序必须不同;跟返回值无关
④程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次。
析构函数语法 ~ 类名() {}
①析构函数,没有返回值也不写void
②函数名称与类名相同,在名称前加上符号~
③析构函数不可以有参数,因此不可以发生重载
④程序在对象销毁前会自动调用析构,无须手动调用而且只会调用一次
案例
/*************************************************************************> File Name: demo09.cpp> Author: 小刘> Description: 封装-推荐写法> Created Time: 2025-08-29 08:32:47************************************************************************/
#include <iostream>
#include <string>
#include <iomanip>using namespace std;class Person
{
public:// 构造函数Person(){cout << "Person的构造函数调用" << endl;}// 析构函数~Person(){cout << "Person的析构函数调用" << endl;}
};void test01()
{Person p; // test01调用的时候,给p分配内存,test01执行结束,销毁p的内存
}int main(int argc ,char *argv[])
{test01();system("pause");return 0;
}