嵌入式学习-C嘎嘎-Day02
嵌入式学习-C嘎嘎-Day02
1. 类和对象(class and object/instance)
类是一个抽象的概念,规定了对象的行为和数据,对象是实体,需要根据类的概念进行创建。
类规定了对象的行为和数据,正式的说法应该是:
- 成员函数(行为):表示对象可以调用的函数,完成一些功能操作,通常使用动词或动词词组命名,比如跑、跳、吃饭、打游戏等......
- 属性/成员变量(数据):表示对象存储的数值,通常使用名词或名词词组命名,比如颜色、身高、价格等.......
成员member(数据成员) = 成员函数 + 成员变量
【例子】以手机为例,来说明类和对象的使用。
C++类命名规范:
自定义类的命名需要遵守帕斯卡命名法(大驼峰命名法):所有单词的首字母大写;
成员使用下划线命名法:所有单词消息,中间使用下划线分割。
#include <iostream>
using namespace std;
/**
* @brief The MobilePhone class
* 手机类
*/
class MobilePhone
{
public: // 公有权限:完全开放
// 品牌、型号、价格
string brand;
string model;
double price;
// 播放音乐、运行游戏、通信
void play_music()
{
cout << "迎面走来的你让我蠢蠢欲动" << endl;
}
void run_game()
{
cout << "原神,启动!" << endl;
}
void communicate()
{
cout << "你好吗?" << endl;
}
};
int main()
{
return 0;
}
基于上面的手机类,完成手机对象的创建。
C++拥有两种对象:
- 栈内存对象
栈内存对象的生命周期与局部变量相同,所在的{}执行完成后,对象自动被销毁,可以搭配引用使用。
- 堆内存对象
必须使用new关键字创建对象,必须使用指针存储对象地址,调用成员时必须使用->,而不是.
如果不手动使用delete销毁,则对象持续存在(内存泄漏)。delete之后就不能继续使用对象了。
#include <iostream>
using namespace std;
/**
* @brief The MobilePhone class
* 手机类
*/
class MobilePhone
{
public: // 公有权限:完全开放
// 品牌、型号、价格
string brand;
string model;
double price;
// 播放音乐、运行游戏、通信
void play_music()
{
cout << "迎面走来的你让我蠢蠢欲动" << endl;
}
void run_game()
{
cout << "原神,启动!" << endl;
}
void communicate()
{
cout << "你好吗?" << endl;
}
};
int main()
{
// 创建一个栈内存对象
MobilePhone mp1;
// 写入成员变量
mp1.brand = "Apple";
mp1.model = "16Pro Max";
mp1.price = 9999;
// 获取成员变量值
cout << mp1.brand << endl;
cout << mp1.model << endl;
cout << mp1.price << endl;
// 调用成员函数
mp1.communicate();
mp1.play_music();
mp1.run_game();
// 创建一个堆内存对象
MobilePhone* mp2 = new MobilePhone;
// 写入成员变量
mp2->brand = "小米";
mp2->model = "15";
mp2->price = 4999;
// 读取成员变量
cout << mp2->brand << endl;
cout << mp2->model << endl;
cout << mp2->price << endl;
// 调用成员函数
mp2->communicate();
mp2->play_music();
mp2->run_game();
// 手动销毁
delete mp2;
return 0;
} // mp1自动被销毁
2. 封装
上面的类与结构体很相似,实际上类需要进行封装,封装可以让类把一些属性和细节隐藏,重新提供给外部相应的调用接口,封装可以提升类的安全性和可维护性,也可以让程序员专注于类的整体而非内部细节。
常用的接口分为读(getter)和写(setter),可以根据不同成员变量的读写需求添加接口,例如某变量只需要外部读取,则只添加getter;某变量需要外部读写,则需要同时添加getter和setter......
#include <iostream>
using namespace std;
class MobilePhone
{
private: // 私有权限:只能在类内访问
string brand = "山寨"; // 只读
string model; // 可读可写
double price; // 只写
public:
string get_brand()
{
return brand;
}
string get_model()
{
return model;
}
void set_model(string m)
{
model = m;
}
void set_price(double p)
{
price = p;
}
};
int main()
{
MobilePhone mp1;
mp1.set_model("16Pro Max");
mp1.set_price(9999);
cout << mp1.get_brand() << endl;
cout << mp1.get_model() << endl;
MobilePhone* mp2 = new MobilePhone;
mp2->set_model("15");
mp2->set_price(4999);
cout << mp2->get_brand() << endl;
cout << mp2->get_model() << endl;
delete mp2;
return 0;
} // mp1自动被销毁
3. 构造函数 constructor
3.1 基本使用
创建一个对象必须调用构造函数,如果程序员不手写构造函数,编译器会自动添加一个没有参数且函数体为空的构造函数。
构造函数在结构上具有以下特点:
- 不写返回值
- 函数名称与类名相同
#include <iostream>
using namespace std;
class MobilePhone
{
private:
string brand = "山寨";
string model;
double price;
public:
MobilePhone()
{
cout << "创建了一个对象" << endl;
}
string get_brand()
{
return brand;
}
string get_model()
{
return model;
}
void set_model(string m)
{
model = m;
}
void set_price(double p)
{
price = p;
}
};
int main()
{
MobilePhone mp1; // 调用构造函数
mp1.set_model("16Pro Max");
mp1.set_price(9999);
cout << mp1.get_brand() << endl;
cout << mp1.get_model() << endl;
MobilePhone* mp2 = new MobilePhone; // 调用构造函数
mp2->set_model("15");
mp2->set_price(4999);
cout << mp2->get_brand() << endl;
cout << mp2->get_model() << endl;
delete mp2;
return 0;
}
构造函数可以重载,如果程序员手动编写任意一个构造函数,编译器都不再添加构造函数。
构造函数除了创建对象外,通常还用于给成员变量初始化。
构造函数本质上是一种特殊的成员函,因此除了重载之外,也支持参数默认值和哑元等功能。
#include <iostream>
using namespace std;
class MobilePhone
{
private:
string brand = "山寨";
string model;
double price;
public:
MobilePhone(string b,string m,double p)
{
// 属性初始化
brand = b;
model = m;
price = p;
cout << "创建了一个对象" << endl;
}
string get_brand()
{
return brand;
}
string get_model()
{
return model;
}
void set_model(string m)
{
model = m;
}
void set_price(double p)
{
price = p;
}
};
int main()
{
MobilePhone mp1("Apple","16 Pro Max",9999); // 调用构造函数
cout << mp1.get_brand() << endl;
cout << mp1.get_model() << endl;
MobilePhone* mp2 = new MobilePhone("小米","15",4999); // 调用构造函数
cout << mp2->get_brand() << endl;
cout << mp2->get_model() << endl;
delete mp2;
return 0;
}
3.2 构造初始化列表
构造初始化列表在现阶段可以认为是一种简便的写法,自行决定是否使用。
#include <iostream>
using namespace std;
class MobilePhone
{
private:
string brand = "山寨";
string model;
double price;
public:
MobilePhone(string b,string m,double p)
:brand(b),model(m),price{p} // 构造初始化列表
{
cout << "创建了一个对象" << endl;
}
string get_brand()
{
return brand;
}
string get_model()
{
return model;
}
void set_model(string m)
{
model = m;
}
void set_price(double p)
{
price = p;
}
};
int main()
{
MobilePhone mp1("Apple","16 Pro Max",9999); // 调用构造函数
cout << mp1.get_brand() << endl;
cout << mp1.get_model() << endl;
MobilePhone* mp2 = new MobilePhone("小米","15",4999); // 调用构造函数
cout << mp2->get_brand() << endl;
cout << mp2->get_model() << endl;
delete mp2;
return 0;
}
3.3 拷贝构造函数
3.3.1 概念
如果程序员不写拷贝构造函数,编译器也会自动添加一个重载的构造函数:拷贝构造函数。
拷贝构造函数实现的功能是,以一个已经存在的对象为基础,新创建的对象与已经存在的对象数据相同,由于封装特性,这两个对象各自是一个整体,数据是两份的。
#include <iostream>
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
double price;
public:
MobilePhone(string b,string m,double p)
:brand(b),model(m),price{p}{}
// 如果不手写拷贝构造函数,编译器默认添加下面的拷贝构造函数
MobilePhone(const MobilePhone& mp)
{
brand = mp.brand;
model = mp.model;
price = mp.price;
}
void show()
{
cout << brand << " " << model << " " << price << endl;
cout << &brand << " " << &model << " " << &price << endl;
}
};
int main()
{
MobilePhone mp1("Apple","16 Pro Max",9999);
// 调用拷贝构造函数
// 新创建的mp2数据与mp1相同
MobilePhone mp2(mp1);
mp1.show();
mp2.show();
return 0;
}
【思考】默认的拷贝构造函数,可能出现什么问题?
当成员变量出现指针时,会破坏面向对象特性,此现象被称为浅拷贝。
3.3.2 浅拷贝
下面的代码中,不通过setter就能修改隐藏的成员变量,这种设计不符合面向对象的封装特性。
#include <iostream>
#include <string.h>
using namespace std;
class MobilePhone
{
private:
char* brand;
string model;
double price;
public:
MobilePhone(char* b,string m,double p)
:brand(b),model(m),price{p}{}
void show()
{
cout << brand << " " << model << " " << price << endl;
}
};
int main()
{
char c[20] = "Apple";
MobilePhone mp1(c,"16 Pro Max",9999);
MobilePhone mp2(mp1);
strcpy(c,"SAMSUNG");
mp1.show();
mp2.show();
return 0;
}
3.3.3 深拷贝
解决浅拷贝的问题,方法就是在每个成员变量指针赋值处,单独在堆区开辟一块区域,使当前创建的对象独占。
#include <iostream>
#include <string.h>
using namespace std;
class MobilePhone
{
private:
char* brand;
string model;
double price;
public:
MobilePhone(char* b,string m,double p)
:brand(new char[20]) // 开辟一个区域单独指向
,model(m),price{p}
{
strcpy(brand,b);
}
MobilePhone(const MobilePhone& mp)
{
brand = new char[20]; // 开辟一个区域单独指向
strcpy(brand,mp.brand);
model = mp.model;
price = mp.price;
}
void show()
{
cout << brand << " " << model << " " << price << endl;
}
};
int main()
{
char c[20] = "Apple";
MobilePhone mp1(c,"16 Pro Max",9999);
MobilePhone mp2(mp1);
strcpy(c,"SAMSUNG");
mp1.show();
mp2.show();
return 0;
}
【思考】上面的代码有什么问题?
new开辟堆内存没有delete,出现了内存泄漏的情况。
3.4 构造函数的几种调用方式
常见的几种创建对象的方式:
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
Student(string name):name(name){}
string get_name()
{
return name;
}
};
int main()
{
Student s1("张三");
cout << s1.get_name() << endl;
Student* s2 = new Student("李四");
cout << s2->get_name() << endl;
delete s2;
Student s3 = Student("王五");
cout << s3.get_name() << endl;
string name = "赵六";
Student s4 = name;
cout << s4.get_name() << endl;
return 0;
}
可以在构造函数前增加explicit关键字屏蔽赋值过程中,编译器帮忙调用构造的优化。
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
// 明确
explicit Student(string name):name(name){}
string get_name()
{
return name;
}
};
int main()
{
string name = "赵六";
// string → Student
// 编译器帮忙调用构造函数,传递name作为构造函数的参数
Student s4 = name;
cout << s4.get_name() << endl;
return 0;
}
4. 析构函数 destructor
析构函数也是一种特殊的成员函数,析构函数是与构造函数对立的函数:
构造函数 | 析构函数 |
创建对象时调用 | 对象销毁时被调用 |
可有参数,能被重载和设置参数默认值 | 没有参数,不能重载和设置默认值 |
函数名为 类名 | 函数名为 ~类名 |
通常用于对象属性初始化 | 通常用于释放并关闭对象资源 |
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
Student(string name):name(name){}
string get_name()
{
return name;
}
// 如果程序员不写,也会添加一个函数体为空的析构函数,如下
// ~Student(){}
~Student()
{
cout << name << "销毁了" << endl;
}
};
int main()
{
Student s1("张三");
Student* s2 = new Student("李四");
delete s2; // s2触发析构函数
cout << "主函数结束" << endl;
return 0;
} // s1触发析构函数
回到3.3.3深拷贝的代码中,可以通过析构函数释放new出来的堆内存。
#include <iostream>
#include <string.h>
using namespace std;
class MobilePhone
{
private:
char* brand;
string model;
double price;
public:
MobilePhone(char* b,string m,double p)
:brand(new char[20]) // 开辟一个区域单独指向
,model(m),price{p}
{
strcpy(brand,b);
}
MobilePhone(const MobilePhone& mp)
{
brand = new char[20]; // 开辟一个区域单独指向
strcpy(brand,mp.brand);
model = mp.model;
price = mp.price;
}
// 析构函数
~MobilePhone()
{
cout << "释放资源:" << brand << endl;
// 释放堆内存资源
delete brand;
}
void show()
{
cout << brand << " " << model << " " << price << endl;
}
};
int main()
{
char c[20] = "Apple";
MobilePhone mp1(c,"16 Pro Max",9999);
MobilePhone mp2(mp1);
strcpy(c,"SAMSUNG");
mp1.show();
mp2.show();
return 0;
}
5. 作用域限定符 ::
5.1 名字空间 namespace
本次学习几乎所有内置的功能都属于C++标准库,放置在std名字空间下,例如字符串类完整的名称应该是std::string
名字空间是用来解决不同的作用域下的重名问题。
#include <iostream>
using namespace std; // 默认使用std::
int a = 1;
// 自定义名字空间
namespace my_space
{
int a = 3;
int b = 4;
}
using namespace my_space;
int main()
{
int a = 2;
// 手动使用std::
std::string str = "今天才周二";
std::cout << str << std::endl;
cout << a << endl; // 2
// “匿名名字空间”
cout << ::a << endl; // 1
cout << my_space::a << endl; // 3
cout << b << endl; // 4
return 0;
}
5.2 类内声明,类外定义
之前类中的成员函数都是在类内直接定义的,在实际开发过程中通常需要声明与定义分离,在类内只声明,函数定义放置到类外。
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
// 类内声明
Student(string n);
string get_name();
void set_name(string n);
~Student();
};
// 函数名前加 类名::
Student::Student(string n):name(n){}
string Student::get_name()
{
return name;
}
void Student::set_name(string n)
{
name = n;
}
Student::~Student()
{
cout << "析构函数" << endl;
}
int main()
{
Student s("张三");
cout << s.get_name() << endl;
s.set_name("张三丰");
cout << s.get_name() << endl;
return 0;
}
5.3 调用静态成员
作用域限定符可以应用在静态成员中,后续讲解。
6. this指针
6.1 概念
this指针是类内的一种特殊指针,存在于成员函数中,存储了当前类对象的首地址。
#include <iostream>
using namespace std;
class Dog
{
public:
void test()
{
// 可以通过this找到当前类外部对象
// 突破作用域
cout << this << endl;
}
};
int main()
{
Dog d1;
cout << &d1 << endl; // 0x61fe8b
d1.test(); // 0x61fe8b
Dog* d2 = new Dog;
cout << d2 << endl;
d2->test(); // 0x1001108
delete d2; // 0x1001108
return 0;
}
6.2 类内调用其他成员
成员必须由对象调用,类内成员函数调用其他成员时,编译器自动添加this找到调用的对象。
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
public:
Dog(string n)
{
// 编译器通过this指针找到对象,调用成员
this->name = n;
}
string get_name()
{
return this->name;
}
void test()
{
cout << this->get_name() << endl;
}
};
int main()
{
Dog d("大黄");
d.test(); // 大黄
return 0;
}
6.3 成员变量与局部变量重名
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
public:
Dog(string name):name(name) // 构造初始化列表也可以区分重名
{
}
void set_name(string name)
{
// 对象调用成员,与局部进行区分
this->name = name;
}
string get_name()
{
return name;
}
};
int main()
{
Dog d("小白");
cout << d.get_name() << endl;
d.set_name("小黄");
cout << d.get_name() << endl;
return 0;
}
6.4 链式调用
如果一个成员函数的返回值是当前类型的引用,则表示此函数return *this,支持链式调用。
#include <iostream>
using namespace std;
class Value
{
private:
int val;
public:
Value(int val):val(val){}
int get_val()
{
return val;
}
Value& add(int i)
{
cout << &val << endl;
val += i;
return *this;
}
};
int main()
{
// 普通调用
Value v1(1);
v1.add(2);
v1.add(3);
v1.add(4);
cout << v1.get_val() << endl; // 10
Value v2(1);
// 链式调用
cout << v2.add(2).add(3).add(4).get_val() << endl; // 10
cout << v2.get_val() << endl; // 10
return 0;
}
7. static关键字
7.1 静态局部变量
使用static修饰局部变量,这样的变量就是静态局部变量。
静态局部变量所在的函数第一次被调用时,静态局部变量被创建,函数执行完成后,此变量不会被销毁,下次调用此函数时,继续使用已经创建的静态局部变量,直到程序运行结束销毁。
#include <iostream>
using namespace std;
class Test
{
public:
void test_static()
{
int a = 1;
static int b = 1;
cout << ++a << endl;
cout << ++b << endl;
cout << endl;
}
};
int main()
{
Test t1;
t1.test_static(); // b创建
t1.test_static();
Test t2;
t2.test_static();
t2.test_static();
return 0;
} // b销毁
7.2 静态成员变量
使用static修饰成员变量,就是静态成员变量。
非const的静态成员变量需要类内声明,类外初始化;一个类的所有对象共用一份静态成员变量。
静态成员变量可以脱离对象使用,因此:
- 程序启动时开辟内存
- 对象销毁后,不销毁静态成员变量
- 程序结束时销毁
建议直接使用 类名:: 的方式调用静态成员变量,因为可以增加代码的可读性。
#include <iostream>
using namespace std;
class Test
{
public:
int a = 1;
// static int b = 1; 错误:非const的静态成员变量不能类内初始化
static int b; // 只声明
};
// 类外初始化
int Test::b = 1;
int main()
{
cout << Test::b << " " << &Test::b << endl; // 1 0x403004
// 局部代码块
{
Test t1;
cout << ++t1.a << " " << &t1.a <<endl; // 2 0x61fe8c
cout << ++t1.b << " " << &t1.b<< endl; // 2 0x403004
Test t2;
cout << ++t2.a << " " << &t2.a << endl; // 2 0x61fe88
cout << ++t2.b << " " << &t2.b << endl; // 3 0x403004
} // t1、t2销毁
cout << Test::b << " " << &Test::b << endl; // 3 0x403004
return 0;
} // Test::b销毁
7.3 静态成员函数
使用static修饰成员函数,就是静态成员函数。
静态成员函数也可以脱离对象,直接使用类名调用,推荐使用这种方式调用。因此静态成员函数没有this指针,不能在静态成员函数中调用其他非静态成员,但是可以调用其他静态成员。
#include <iostream>
using namespace std;
class Demo
{
public:
int a = 1;
void test()
{
cout << "成员函数" << endl;
}
static int b;
static void func(int)
{
cout << "静态成员函数2" << endl;
}
static void func()
{
cout << "静态成员函数" << endl;
// cout << this << endl; 错误
// cout << a << endl; 错误
// test(); 错误
cout << b << endl;
func(2);
}
};
int Demo::b = 1;
int main()
{
Demo::func(); // 推荐
Demo d;
d.func();
}
通常一些偏向于功能性接口(简单且直接)的设计考虑采用静态成员函数。
7.4 static的应用举例——单例模式
设计模式是一套被反复使用、多数人知晓的、经过分类的代码设计经验的总结。理论上讲,任何一个面向对象的编程语言都可以学习设计模式。
单例模式是设计模式中最简单的一种,本节通过static的性质,分别使用指针和引用编写两个简化版的单例模式案例说明static的应用。
单例模式设计的类可以确保在使用的过程中始终存在一个对象。
基于指针的单例模式:
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton(){}
Singleton(const Singleton&){}
static Singleton* instance;
public:
// 脱离对象调用的静态成员函数:目的是获得对象
static Singleton* get_instance()
{
if(instance == NULL)
instance = new Singleton;
return instance;
}
};
Singleton* Singleton::instance = NULL;
int main()
{
cout << Singleton::get_instance() << endl;
cout << Singleton::get_instance() << endl;
}
基于引用的单例模式:
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton(){}
Singleton(const Singleton&){}
public:
// 脱离对象调用的静态成员函数:目的是获得对象
static Singleton& get_instance()
{
static Singleton instance;
return instance;
}
};
int main()
{
cout << &Singleton::get_instance() << endl;
cout << &Singleton::get_instance() << endl;
}
8. const关键字
8.1 修饰成员函数
const修饰的成员函数,表示常成员函数。
常成员函数可以调用非const的成员变量,但是不能修改数值;不能调用非const的成员函数。
#include <iostream>
using namespace std;
class Computer
{
private:
string brand = "联想";
public:
void set_brand(string brand)
{
this->brand = brand;
}
string get_brand() const
{
return brand;
}
void test() const
{
cout << brand << endl;
// brand = "惠普"; 错误
// set_brand("戴尔"); 错误
cout << get_brand() << endl;
}
};
int main()
{
Computer c;
c.test();
}
尽量把成员函数设置为常成员函数,使代码更加严谨,例如getter函数。
8.2 修饰对象
const修饰对象,表示该对象为常量对象,这样的对象不能修改成员变量的数值,也不能调用非const的成员函数。
#include <iostream>
using namespace std;
class Computer
{
private:
string brand;
public:
string system = "Win11";
Computer(string brand):brand(brand){}
void set_brand(string brand)
{
this->brand = brand;
}
string get_brand() const
{
return brand;
}
};
int main()
{
// 常量对象
const Computer c1("惠普");
cout << c1.system << endl;
// c1.system = "Win12"; 错误
// c1.set_brand("苹果"); 错误
cout << c1.get_brand() << endl;
Computer const c2("戴尔");
cout << c2.system << endl;
// c2.system = "Win95"; 错误
// c2.set_brand("华硕"); 错误
cout << c2.get_brand() << endl;
}
8.3 修饰成员变量
const修饰的成员变量是常成员变量,表示改成员变量的值不可变。
常成员变量的初始值可以通过两种方式设定:
- 直接初始化
- 构造初始化列表
上述两者同时使用时,以后者为准。
#include <iostream>
using namespace std;
class Pig
{
private:
const string variety = "黑猪"; // 品种
const int weight;
public:
Pig(string v,int w):variety(v),weight(w){}\
Pig(int w):weight(w){}
void test()
{
// variety = "荷兰猪"; 错误
// weight = -1; 错误
cout << variety << endl;
cout << weight << endl;
}
};
int main()
{
Pig p("GG Bond",100);
p.test();
Pig p2(90);
p2.test();
}
8.4 修饰引用参数
详见第一天的引用参数章节。
引用参数在应该能被添加为const的情况下,尽量添加const修饰,以达到引用参数的安全性。
8.5 constexpr关键字-C++11
传统的const没有编译和运行的概念,而constexpr是一种在编译时确定的表达式。也就是说,编译器看到constexpr就可以进行优化了。
#include <iostream>
using namespace std;
class Test
{
public:
int a = 1;
const int b = 2;
// constexpr int c = 3; 错误
const static int d = 4;
constexpr static int e = 5;
};
int main()
{
Test t;
cout << t.a << endl;
cout << t.b << endl;
cout << t.d << endl;
cout << t.e << endl;
}
上面代码中,非静态的变量abc要跟对象绑定,对象的创建严格的讲是在运行时发生的,因此上面的变量c在编译时无法确定,这与constexpr的含义冲突,编译出错。
#include <iostream>
#include <array>
using namespace std;
constexpr int func(int i)
{
return i+1;
}
int main()
{
// 创建一个长度为3的int数组
// <>的第二个参数(本例中的3)必须在编译时确定
array<int,3> arr;
int i = 4;
cout << func(i) << endl; // 5
array<int,func(2)> arr2;
// array<int,func(i)> arr2; 错误
}
在实际的使用过程中,constexpr的受限较多,请根据实际情况决定是否使用。
原文地址:https://blog.csdn.net/Xiaomo1536/article/details/143806377
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!