Friday, 15 March 2024
This blog will be used by the Release Team for communally maintained projects which need a release announcement.
KDE Frameworks, Plasma and KDE Gear will remain on kde.org. But individual releases of apps and libraries which get their own releases can be announced here.
Ruqola 2.1.1 is a bugfix release of the Rocket.chat app.
Improvements:
- Preview url fixed
- I implement "block actions" (it's necessary in RC 6.6.x when we invite user)
- Fix OauthAppsUpdateJob support (administration)
- Fix update view when we translate message (View was not updated in private channel)
- Show server icon in combobox server
- Fix show icon for displaying emoji popup menu when we display thread message
- Fix jitsi support
- Fix dark mode
URL: https://download.kde.org/stable/ruqola/
Source: ruqola-2.1.1.tar.xz
SHA256: 6f089271ef9f2f576aa31ddc404bdbc8ddd5ddecba3cd9ff829641123bceb0ae
Windows Build: ruqola-2.1.1-windows-cl-msvc2022-x86_64.exe
Signed by: E0A3EB202F8E57528E13E72FD7574483BB57B18D Jonathan Esk-Riddell jr@jriddell.org
https://jriddell.org/esk-riddell.gpg
Sunday, 10 March 2024
If you have not been following this blog series, I made a wrapper for Firefox to be able to run different tabs (and more) in different KDE Plasma Activities.
Often a hurdle to using a piece of software is that it is not packaged for Linux distros.
Kudos to Aurélien Couderc (coucouf), who packaged already 0.4.1 for Debian and provided the patch to make it easier to package to different distros.
With 0.4.2 version of Activity-aware Firefox we applied that patch. Other then that, the functionality remains the same as in 0.4.1.
Then I also wrote an AUR package, so Arch, EndeavourOS etc. should be covered now too.
As a consequence, Repology now lists 12 distro packages for Activity-aware Firefox – that is a great start!
But while large, Debian- and Arch-based distros are just a subset of all available FOSS operating systems that KDE Plasma and Firefox run on. If someone were to put it on Open Build Service to cover also RPM-based and other distros, that would be a great boon!
Contributions welcome, as I am reaching the limit of my skills here.
hook out → server migration successful – more on that some other day
Friday, 8 March 2024
生成个人访问令牌
生成secrets前首先要生成个人访问令牌
创建令牌
- 验证您的电子邮件地址(尚未验证)
- 在任何界面的右上角,点击个人资料照片,点击设置
- 在左边侧边栏中,点击
Developer settings(在最下边) - 然后在
Personal access tokens的Tokens中选择Generate new tokens然后选择第二个
选则您要授予此令牌的范围或权限。要使用令牌从命令行访问存储库,请选择repo。如果还需要其他权限请自行勾选。(workflow也进行选择),然后确定即可
最后
将令牌复制到剪切板。出于安全的原因,在您离开该界面后,您将无法再次看到该令牌。
配置github项目的secrets
在github的仓库中打开设置。
在侧边栏中点击Secrets and variables中的Actions。
点击New repository secret。
名称是随便起得。Secret就是刚才生成的令牌.
更改该仓库的写权限
在该仓库侧边栏中选择Actions当中的General,在该页面下的Workflow当中选择第一个并前保存.
设置贪吃蛇
在仓库中的Actions中点击newworkflow
然后点击set up a workflow yourself
新建snake.yml.
复制以下内容.
1 | name: generate animation |
然后保存.
然后点击Run workflow即可
然后回到该仓库的主页,会生成output分支,里面会有对应提交记录的贪吃蛇的svg图片
然后点击生成的文件,在右上角有Raw,即可查看文件地址.
将贪吃蛇部分代码部分代码的链接进行替换。
Monday, 4 March 2024
C++初始
1.编写C++程序步骤
- 创建项目
- 创建文件
- 编写代码
- 运行程序
1.3变量
作用:给一段指定的内存空间起名,方便操作这段内存
语法:数据类型 变量名 =初始值
1.5关键字
预先保留的单词
在定义变量或者常量的时候,不要用关键字
| asm | do | if | return | typedef |
|---|---|---|---|---|
| auto | dynamic_cast | inline | short | typeid |
| bool | else | int | signed | typename |
| break | enum | long | sizeof | union |
| case | explicit | mutable | static | unsigned |
| catch | export | namespace | static_cast | using |
| char | extern | new | struct | virtual |
| class | false | operator | switch | void |
| const | float | private | template | volatile |
| const_cast | for | protected | this | wchar_t |
| continue | friend | public | throw | while |
| default | goto | register | true | |
| delete | double | reinterpret_cast | try |
标识符命名规则
标识符不能是关键字
标识符只能由字母,数字,下划线组成
第一个字符必须为字母或者下划线
标识符中区分大小写
给标识符命名时,争取做到见名知意的效果,方便阅读
数据类型
实型
作用:用于表示小数
浮点型变量分为两种:
- 单精度float
- 双精度double
两者的区别在与表示的有效数字范围不同.
| 数据类型 | 占用空间 | 有效数字范围 |
|---|---|---|
| float | 4字节 | 7位有效数字 |
| double | 8字节 | 15 ~ 16位有效数字 |
也可以用科学计数法表示.
字符型
作用:字符型变量用于显示单个字符
语法:char ch = 'a';
注意1:在显示字符型变量时,用单引导将字符括起来,不要用双引号
注意2:单引号内只能有一个字符,不可以是字符串
字符串型
1 | //c风格的字符串 |
布尔类型
本质上1代表真,0代表假
占一个字节.
数据的输入
关键字cin
1 | //整形 |
小数间是不能做取模运算的
程序流程结构
c/c++支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构
- 顺序结构:程序按顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能
- 循环结构:依据条件是否满足,循环多次执行某段代码
跳转语句
break语句
作用:用于跳出选择结构或者循环结构
break使用的时机:
- 出现在switch条件语句中,作用是终止case并跳出switch
- 出现在循环语句中,作用是跳出当前的循环语句
- 出现在嵌套循环中,跳出最近的内存循环语句
continue 语句
作用:在循环语句中,跳出本次循环中余下尚未执行的语句,继续执行下一次循环
函数
作用:将一段经常使用的代码封装起来,减少重复的代码
函数的定义一般主要有5步骤:
- 返回值类型
- 函数名
- 参数列表
- 函数体语句
- return 表达式
指针
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
结构体
核心编程
内存分区模型
代码区 存放CPU执行的机器指令 共享 只读
全局区
局部变量、const修饰的局部变量(局部变量) 不在全局区中 全局变量 静态变量 static关键字 常量 全局区 栈区
不要返回局部变量的地址
堆区
在c++中主要利用new在堆区开辟内存
意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
堆区
C++中利用new操作符在堆区开辟数据
堆区开辟的数据由程序员手动开辟、手动释放、释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
new的基本语法
1 | int *a=new int(10); |
引用
作用:给变量起别名
语法:数据类型 &别名 =原名
注意事项
- 引用必须要初始化
- 引用一旦初始化后,就不可以更改
引用可以简化指针的用法.
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
通过引用参数产生的效果按地址传递是一样的。引用的语法更加的简单。
引用是可以作为函数的返回值存在的.
不返回局部变量的引用.
函数的调用可以作为左值.
1 | int &ref2=test02(); |
本质:引用的本质在c++内部实现是一个指针常量.
1 | void func(int &ref) |
引用必须引一块合法的内存空间。
const修饰的局部变量不再全局区.
函数
函数默认参数
在c++中,函数的形参列表中的形参是可以有默认值的
语法 :返回值类型 函数名 (参数= 默认值) {}
1 | int func(int a,int b=10,int c=10) |
函数的默认参数,如果我们传入了参数,值就使用我们传入的参数,如果我们没有传入参数,那么就使用函数的默认参数值
注意事项:
- 如果某个位置已经有了默认参数,那么这个位置往后,从左到右都必须要有默认值
- 如果函数声明有默认参数,函数实现就不能有默认参数
- 声明和实现只能一个有默认参数
函数占位参数
c++中的函数的形参列表可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型) { }
目前占位参数我们还用不到.
占位参数还可以有默认参数.
函数重载
作用:函数名可以相同,提高复用性
满足条件
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者顺序不同
注意事项:
函数的返回值不可以作为函数重载的条件
引用作为重载的条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void fun(int &a)//这两个可以作为函数重载的条件,因为函数的参数类型不同
{
cout << "func(int &a)调用" <<endl;
}
void fun(const int &a)
{
cout << "func(const int &a)调用"<<endl;
}
int main()
{
int a=10;
func(a);//调用的是第一个,因为这是一个变量.
int func(10);//调用的是第二个。
return 0;
}函数重载碰到默认参数(会出现二义性,尽量避免使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void func2(int a)
{
cout << "func2(int a)的调用" <<endl;
}
void func2(int a,int b=10)
{
cout << "func2(int a,int b)的调用" <<endl;
}
int main()
{
func2(10);
return 0;
}
类和对象
c++面向对象的三大特性为:封装、继承、多态
c++认为万事万物都皆为对象,对象上有其属性和行为
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
封装
意义
封装是c++面向对象的三大特性之一
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义1:
在设计类的时候,属性和行为写在一起,表示事物
语法:class 类名 { 访问权限 : 属性 /行为 };
1 | const double PI =3.14; |
1 | class Student//设计学生类 |
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
三种权限:
1.public 公共权限
2.protected 保护权限
3.private私有权限
1 | //访问权限 三种 |
class 和 struct 的区别
在c++中struct 和 class 唯一的区别就在于默认的访问权限不同
区别:
- struct默认权限为公共
- class 默认权限为私有
成员属性设置为私有
优点:
- 将所有成员属性设置为私有,可以自己控制读写权限
- 由于写权限,我们可以检测数据的有效性
在类中可以让另外一个类作为类中本来的成员
1 | class Point |
1 | //在头文件中可以进行类的声明 |
对象的初始化清理
c++中的面向对象来源于生活 ,每个对象也都会有初始设置 以及对象销毁前的清理数据的设置。
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会给我们提供空实现
构造函数语法: 类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前自动调用析构,无需手动调用,而且只会调用一次
构造函数的分类及调用:
按参数
- 有参构造
- 无参构造
按类型
- 普通构造
- 拷贝构造
三种调用方法:
括号法
1
2
3Person P;//默认构造时后面不要加括号
Person p1(10);
Person p2(p1);显示法
1
2
3Person p;
Person p1=Person(10);
Person P2=Person(p2);隐式转换法
1
2Person p4=10;
Person p5=p4;
构造函数的调用顺序:
1.当普通构造一个对象时,程序先自动调用默认构造函数分配空间,在调用自定义构造函数(如果有的话)
2.当拷贝构造一个对象时,程序先自动调用默认构造函数分配空间,然后分两种情况,如果程序有自定义拷贝函数,则调用自定义拷贝构造函数;如果没有,就调用默认拷贝构造函数进行浅拷贝.
拷贝构造函数调用时机
c++拷贝构造函数调用时机
- 使用一个创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值(做参数)
- 以值的方式返回局部对象(做返回值)
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造参数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性值进行拷贝
规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会在提供其他的构造函数
深拷贝与浅拷贝
浅拷贝:简单的赋值操作
深拷贝:在堆区重新申请空间,进行拷贝操作
1 | class Person |
会出现:free():double free detected in tcache 2.
编译起提供的拷贝构造函数,只是简单的赋值,将原先的地址在复制一份。
所以需要深拷贝来解决该问题,自己实现拷贝构造函数
初始化列表
作用:c++提供了初始化列表语法,用来初始化(类的)属性
语法:构造函数( ): 属性1(值1),属性2(值2) ... {}
1 | class Person |
初始化const成员变量的唯一方法就是使用初始化列表
类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称该成员为对象成员
1 | class A{} |
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
先构造A对象.
当其他类的对象作为本类成员,构造时先构造类对象,在构造自身.
先析构B对象
析构时先析构自身,在析构类对象.
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,成为静态成员
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量,不可以访问非静态成员变量
- 无法区分到底是哪一个对象的属性
- 也是有访问权限的
1 | class Person |
1 | class Person |
c++对象模型和this指针
成员变量和成员函数分开存储
类的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
空对象的大小为1
c++编译器会给每一个空对象分配一个字节空间,是为了空对象占内存的位置.
每个空对象有一个独一无二的内存地址
this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题.this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态的成员函数内的一种指针
this指针不需要定义,直接使用即可
用途:
- 当形参和成员变量名同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
1 | class Person |
空指针访问成员函数
c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
对this判空有助于提高代码的健壮性
1 | class Person |
const修饰成员函数
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
- 普通成员函数可以修改属性
1 | //this指针的本质 是指针常量 指针的指向是不可以修改的 |
在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改.
友元
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是一个函数或者类 访问另一个类中私有成员
友元的关键字:friend
友元的三种实现
全局函数做友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class Building
{
//goodGay全局函数是 BUilding好朋友 ,可以访问Building中私有成员
friend void goodGay(Building &building);
public:
Building()
{
m_SittingRoom ="客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodGay(Building &building)
{
cout << building.m_SittingRoom << endl;
cout << building.m_BedRoom << endl;
}
void test01()
{
Building building;
goodGay(building);
}类做友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45class Building
{
//goodGay类是本类的好朋友,可以访问本类中的私有成员
friend class goodGay;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
class goodGay
{
public:
goodGay();
void visit();//参观函数访问Building中的属性
Building *building;
};
goodGay::goodGay()
{
building = new Building;
}
void goodGay:: visit()
{
cout<<building->m_SittingRoom<<endl;
cout<<building->m_BedRoom<<endl;
}
//类外去写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
void test01()
{
goodGay g;
g.visit();
}成员函数做友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49class Building;
class goodGay
{
public:
goodGay();
void visit1();//visit可以访问私有成员
void visit2();//不可以访问私有成员
Building *building;
};
class Building
{
friend void goodGay::visit1();
public:
Building();
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
Building :: Building()
{
m_SettingRoom = "客厅";
m_BedRoom = "卧室";
}
goodGay :: goodGay()
{
building = new Building;
}
void goodGay :: visit1()
{
cout << "Building " << building->m_SettingRoom <<endl;
cout << "Building " << building->m_BedRoom <<endl;
}
void goodGay :: visit2()
{
cout << "Building " << building->m_SettingRoom <<endl;
}
void test()
{
goodGay g;
g.visit1();
g.visit2();
}
运算符重载
元算符重载概念:对已有的运算符重新进行定义,赋予其另外一种功能,以适应不同的数据类型
加号运算符重载
作用:实现两个自定义数据类型相加的运算
1 | Person p3=p1.operator+(p2); |
运算符重载也可以发生函数重载.
总结:
- 对于内置的数据类型的表达式的运算符是不可能改变的
- 不要滥用运算符重载
左移运算符重载
可以输出自定义的数据类型
通常不会利用成员函数重载<<运算符,因为无法实现cout在左侧
1 | class Person |
重载左移运算符可以配合友元实现输出自定义数据类型
递增运算符重载
作用:通过重载运算符,实现自己的整形数据
1 | class MyIntegar |
赋值运算符重载
c++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符 operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
1 | class Person |
关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
1 | class Person |
函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
1 | class Myprint |
1 | void test02() |
继承
继承是面向对象的三大特性之一
我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候考虑利用继承技术,减少代码重复。
基本语法
class 子类 : 继承方式 父类
子类 也称为 派生类
父类 也称为 基类
1 | class BasePage |
好处:减少重复代码。
继承方式
一共有三种:
公共继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : public Base1
{
public:
void func()
{
m_A = 10;
m_B = 10;
//m_C = 10;父类中的私有权限成员 子类访问不到
}
};保护继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//父类中的除私有权限下的成员之外的所有成员变为自己的保护权限下的成员
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 :protected Base2
{
public:
void func()
{
m_A=10;
m_B=10;
}
};私有继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//除私有成员之外的全部变为自己的私有成员
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 :private Base3
{
public:
void func()
{
m_A=10;
}
};
继承中的对象模型
问题:从父类继承过来的成员,那些属于子类对象中?
1 | class Base |
父类中的所有非静态成员属性都会被子类继承下去
父类中私有成员属性 是被编译器隐藏了,因此访问不到 但是确实是被继承下去了
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后
1 | class Base |
Base
Son
Sonxg
Basexg
继承中的构造和析构顺序:
- 构造先构造父类,在构造子类
- 析构先析构子类,在构造父类
继承中同名处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?
- 访问子类同名成员直接访问即可
- 访问父类同名成员 需要加作用域
1 | void test() |
1 | //同名函数的处理方式 |
如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中的所有同名成员函数。
如果想访问到父类中被隐藏的同名成员函数,需要加作用域。
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中的所有同名的成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
1 | //同名静态成员属性 |
子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
如果想访问父类中被隐藏同名成员,需要加作用域
多继承语法
C++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1 ,继承方式 父类2 ……
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
1 | class Son :public Base1,public Base2 |
总结:多继承中如果父类中出现了同名情况,子类使用时候要加作用域.
菱形继承
菱形继承概念:
两个派生类继承同一个基类
又有某个同类同时继承两个派生类
这种继承被称为菱形继承,或者钻石继承
菱形继承典型案例
动物下有羊 与 驼继承了动物
底下又有草泥马(羊驼)同时继承了羊 与 驼
菱形继承问题:
1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
2.草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以.
利用虚继承 解决菱形继承问题
1 | class Animal |
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题
多态
多态是C++面向对象三大特性之一
多态分为两类:
- 静态多态 :函数重载和运算符重载属于静态多态,复用函数名
- 动态多态 :派生类和虚函数实现运行时多态
区别:
- 静态多态的函数地址早绑定 – 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 – 运行阶段确定函数地址
动态多态满足条件:
有继承关系
子类要重写父类中的虚函数(函数返回值类型 函数名 参数类表 完全相同)
使用
父类的指针或者引用 执行子类的对象
C++允许子类父类做类型转换.
1 | class Animal |
原理深度剖析
1 | class Animal |
案例–计算器类
多态的优点:
- 代码结构清晰
- 可读性强
- 立于前期和后期的拓展以及维护
纯虚函数和抽象类
在多态中,通常父类中函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类型
1 | class Base |
案例 – 饮料制作
1 | class AbstractDrinking |
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
区别
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
1 | class Animal |
1 | //虚析构语法: |
总结:
- 虚析构或者纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写
- 拥有纯虚析构函数的类也属于抽象类
案例3 – 组装电脑
1 | class CPU |
文件
通过文件可以将数据持久化
头文件
文件类型:
- 文本文件 -文件以文本的Ascll码形式存储在计算机中
- 二进制文件 -文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂
操作三大类:
- ofstream 写操作
- ifstream 读操作
- fstream 读写操作
文本文件
| 打开方式 | 解释 |
|---|---|
| ios::in | 为读文件而打开文件 |
| ios::out | 为写文件而打开文件 |
| ios::app | 追加方式写文件 |
| ios::trunc | 如果文件存在先删除,在创建 |
| ios::binary | 二进制方式 |
| ios::ate | 初始位置:文件尾 |
文件打开方式可以配合使用, 利用|操作符
总结:
- 文件操作必须包含头文件fstream
- 读文件可以利用ofstream 或者 fstream类
- 打开文件时需要指定操作文件的路径,以及打开的方式
- 利用 << 可以向文件中写数据
- 操作完毕,关闭文件
读文件
1 | void test() |
总结:
- 读文件可以利用fstream ,或者fstream类
- 利用is_open函数可以判断文件是否可以打开成功
- close关闭文件
二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型 : ofstream& write(const char *buffer ,int len)
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
1 | class Person |
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型: istream& read(char *buffer , int len);
参数解释:字符指针buffer指向内存中的一段存储空间,len是读写的字节数
1 | class Person |
c++提高
- 本阶段主要针对C++泛型编程和STL技术详解
模板
模板就是建立通用的模具,大大提高复用性
特点:
- 模板不可以直接使用,他只是一个框架
- 模板的通用并不是万能的
函数模板
- c++另一种编程思想为泛型编程,主要利用的技术就是模板
- c++提供两种模板机制:函数模板和类模板
1 | template<typename T> |
解释:
- template — 声明创建模板
- typename —表明其后面的符号是一种数据类型,可以用class代替
- T — 通用的数据类型 , 名称可以替换 , 通常为大写字母
1 | template <typename T>//声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用的数据类型 |
总结:
- 函数模板利用关键字template
- 函数模板的两种使用方式:1.自动类型推导 2.显示指定类型
- 模板的目的是为了提高复用性,将数据类型参数化
注意事项
- 自动类型推导,必须要推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
1 | template <typename T> |
案例
利用函数模板封装一个函数,使得函数可以对不同的数据类型排序
排序按照从小到达的顺序
可以用到不同的排序函数
1 | template <typename T> |
普通函数与函数模板的区别
区别:
- 普通函数调用时可以发生类型转换(隐式类型转换)
- 函数模板调用时,如果是自动类型推导的话,不会发生类型转换
- 如果显示指定类型的话,可以发生隐式类型转换
1 | template <typename T> |
总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T.
普通函数和函数模板的调用规则
调用规则:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空函数参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
1 | template<typename T> |
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
模板的局限性
- 模板的局限性并不是万能的
1 | template <typename T> |
因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
1 | class Person |
总结:
- 利用具体化的模板,可以解决自定义类型的通用化
- 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
类模板
作用:建立一个通用的类,类中的成员 数据类型可以不具体指定,用一个虚拟的类型来代表
语法
1
2 template <typename T>
类template —声明创建模板
typename —表明其后面是一种数据类型,可以用class代替
T —通用的数据类型,名称可以替换,通常为大写的数据类型
类模板和函数模板的语法相似,在template后面加类,此类称为类模板.
类模板与函数模板的区别
- 类模板没有自动类型的推导的使用方式
- 类模板在模板参数列表中可以有默认参数(仅限于类模板)
1 | template <class T1,class T2=int>//模板参数列表 |
总结:
- 类模板使用只能显示指定类型方式
- 类模板中的模板参数列表可以有默认参数
类模板中成员函数的创建时机
类模板中成员函数和普通类中成员函数创建时间是有区别的
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
1 | template <typename T> |
总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建
类模板对象做函数参数
类模板实例化的对象 ,向函数传参的方式
三种传参方式
指定传入的类型 —直接显示对象的类型
1
2
3
4
5
6
7
8
9void print1(Person <string,int>&p)
{
p.showPerson();
}
void test01()
{
Person<string,int>p("孙悟空",100);
}参数模板化 —将对象中的参数变为模板进行传递
1
2
3
4
5
6
7
8
9
10
11
12
13template <class T1,class T2>
void print2(Person <T1,T2>&p)
{
p.showPerson();
cout << "T1的类型为: " << typeid(T1).name << endl;
}
void test02()
{
Person <string,int >p("tom",200);
print2(p);
}整个类模板化 —将这个对象类型 模板化进行传递
1
2
3
4
5
6
7
8
9
10
11template <class T>
void print3(T &p)
{
p.showPerson();
}
void test03()
{
Person<string ,int >p("唐僧",18);
print3(p);
}
1 | template <class T1 ,class T2> |
总结:
- 通过类模板创建的对象,可以有三种方式向函数中进行传参
- 使用比较广泛的是第一种:指定传入类型
类模板与继承
注意:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需要变为类模板
1 | template <class T> |
如果父类是一个类模板,子类在继承的时候必须要指定出父类中T的数据类型。
类模板成员函数在类外实现
1 | template <class T1,class T2> |
总结:类模板中的成员函数类外实现时,要加上模板参数列表.
类模板分文件编写
类模板成员函数创建实际是在调用阶段 ,导致分文件编写时链接不到
- 直接包含.cpp源文件
- 将声明和实现写到同一个文件当中,并更改后缀名为.hpp. .hpp是约定的名称,并不是强制的
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
总结:主流的解决方式是第二种,将类模板和成员函数写到一起,并将后缀名改为.hpp
类模板与友元
全局函数实现 – 直接在类内声明友元即可
全局函数类外实现 – 提前让编译器知道全局函数的存在
1 | template <class T1,class T2> |
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别
总结: 字符串拼接的重载版本很多,记住几种即可.
myls
ls是一个常用的命令行工具,用于列出指定目录中的文件和子目录。ls的基本语法是:
1 | ls [选项] [文件或目录] |
以下是一些常用的 ls 命令选项:
-l:以长格式显示文件信息,包括文件类型、权限、所有者、组、大小、修改时间等。
1
ls -l
-a:显示所有文件,包括隐藏文件(以点开头的文件)。
1
ls -a
-r:与
-l逆序输出文件信息。1
ls -r
-R:递归显示子目录中的文件。
1
ls -R
-t:按修改时间排序,最新修改的文件显示在前面。
1
ls -lt
-S:按文件大小排序,最大的文件显示在前面。
1
ls -lS
-i:显示文件的 inode 号码。
1
ls -i
思路
1.-i -a -R -r -i -s -l为命令行参数,我们首先需要解析命令行参数。
2.我们需要对以上参数进行了解,并进行构思一个框架以便后续对代码的维护与可读性。
3.我觉得可以将以上参数分为三类:1.-a 为确定需要显示的文件多少,例如-a包含 . 隐藏文件。2.-s -t 排序参数,如果含有该参数,则就需要对文件显示进行排序。3.-i -l 为输出参数,即在输出的时候进行判断是否需要输出对应的信息.
4.ls 还可以指定文件进行查看,我们还需要对不同的文件或目录进行操作.
相关函数
getopt,getcwd
opendir,readdir,closedir
snprintf
stat,lstat
主函数
1 | enum order { |
了解了ls的基本语法与功能后,我们就需要进行实现.
那么就首先需要解析命令行参数.获取我们本次操作需要进行的工作.
getopt
该函数专门用来解析命令行参数.
1 | int getopt(int argc, char * const argv[], const char *optstring); |
参数说明
argc:通常由 main 函数直接传入,表示参数的数量
argv:通常也由 main 函数直接传入,表示参数的字符串变量数组
optstring:一个包含正确的参数选项字符串,用于参数的解析。例如 “abc:”,其中 -a,-b 就表示两
个普通选项,-c 表示一个必须有参数的选项,因为它后面有一个冒号.
这里命令行参数已经解析,那么我们接下来就要进行对要查看的文件或目录数量进行统计.
如果数量大于2,就需要在进行操作前输出本次查看的文件或目录路径.
getcwd
1 | #include<unistd.h> |
介绍:
参数说明:getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。
如果未传入文件或目录,就需要对当前路径下的文件进行操作.所以需要用到该函数.
现在已经获取到需要执行的参数和文件,接下来就需要进行参数操作.
所有参数中最难的就是递归部分,所以我们单独为-R封装一个函数来进行递归操作.
do_open
1 | struct fileinfo//由于-l需要输出文件的详细信息,所以需要结构体来存储信息 |
既然获取到了参数与文件,所以我们的do_open就需要接受这两类参数.
我们在do_open中进行文件打开,排序参数,以及展示.
count参数用于计数文件个数,便于输出结果.
do_open_R
1 | void do_open_R(char *path, int orders[]) |
由于-R参数需要递归,所以采取堆上开辟空间进行存储
这里多了一个判断,如果存储的路径对应的仍为目录,则需要继续进行简单的do_open操作。
所以S_ISDIR用来判断是否是目录,如果是则进行递归操作。
S_ISLNK用来判断文件是否为符号链接,如果不加该判断,会在对 / 目录下/dev/fd中的符号链接进行叠加路径而出现错误.
ls_a
1 | void ls_a(char *path, struct fileinfo *infos, int *count) |
由于需要将信息进行存储,所以ls_a需要接收结构体数组,count 用于记录文件个数.
opendir
opendir()函数用于打开一个目录,并返回指向该目录的句柄,供后续操作使用。
1 |
|
readdir
readdir()函数从一个目录流中读取连续的条目.
1 |
|
每调用 readdir()一次,就会从 dirp 所指代的目录流中读取下一目录条目,并返回一枚指针, 指向经静态分配而得的 dirent 类型结构,内含与该条目相关的如下信息: 每次调用 readdir()都会覆盖该结构。
closedir
closedir()函数用于关闭处于打开状态的目录,同时释放它所使用的资源.(一定要记得关闭文件,否则就会造成内存泄漏).
1 | int closedir(DIR *dirp); |
stat (获取文件信息)
1 |
|
函数参数及返回值含义如下:
- pathname:用于指定一个需要查看属性的文件路径。
- buf:struct stat 类型指针,用于指向一个 struct stat 结构体变量。调用 stat 函数的时候需要传入一个 struct stat 变量的指针,获取到的文件属性信息就记录在 struct stat 结构体中。
- 返回值:成功返回 0;失败返回-1,并设置 error。
struct stat
1 | struct stat |
snprintf
snprintf 是一个 C 语言标准库函数,用于格式化字符串并将结果存储到一个字符数组中
1 | int snprintf(char *str, size_t size, const char *format, ...); |
tr: 指向存储结果的字符数组的指针。size: 最大允许写入的字符数(包括字符串终止符)。format: 格式化字符串,包含了要被写入到字符串中的文本和格式说明符。...: 可变数量的参数,用于替换格式字符串中的格式说明符。
snprintf 的行为类似于 printf,但是它会限制输出字符的数量,以防止溢出缓冲区。如果成功,snprintf 返回写入字符的总数(不包括字符串终止符),如果输出被截断,它会返回实际尝试写入的字符数。如果发生错误,返回负值。
完整代码
1 |
|
malloc&free
malloc(0)返回一个有效的空间长度为0的内存首地址,但是没法用(只能进行申请和释放).
动态申请数组指针:
1
2 int (*P)[3]=(int(*)[3])=malloc(sizeof(int)*3);
int (*q)[2][3]=(int(*)[2][3])malloc(sizeof(int)*6);
初始化:
malloc函数分配得到的内存是未初始化的.一般在使用该内存空间时,要调用memset来初始化为0.
1 | void* memset(void *dest,int c,size_t count); |
该函数可以将指定的内存空间按字节单位置为指定的字符c。其中,dest为要清零的内存空间的首地址,c为要设定的值,count为被操作的内存空间的字节长度。
1 | void* memcpy(void* dest, void* src, size_t count); |
此函数也是按照字节进行拷贝的,dest指向目标地址的指针,也就是要被赋值的空间首地址;src指向源地址的指针,也就是要被复制的空间的首地址;count跟memset()一样表示被拷贝的字节数;返回值也是被赋值的目标地址的指针。
其他申请方式:
calloc
1 | void* calloc(size_t num, size_t size); |
realloc
1 | void* realloc(void* memblock, size_t size); // 为已分配的内存空间重新申请内存块 |
_msize
1 | size_t _msize(void* memblock); // Windows平台下专用函数,非C语言标准函数 |
返回malloc() & calloc() & realloc()等申请内存块的大小,参数是分配内存块的首地址,也就是malloc() & calloc() & realloc()等的返回值。
free
用malloc()申请一块内存空间,OS会有一张表记录所申请空间的首地址和这块地址的长度,free(空间首地址),free会从表中查找到这块首地址对应的内存大小,一并释放掉。
- free()不能去释放栈区的空间,栈区空间是由OS管理的,由OS进行申请和释放。
- 释放空间后,指针需要置空,避免成为野指针。
1 | int* q = (int*)malloc(3); |
new & delete
new
new 在申请基本类型空间时,主要会经历两个过程:
- 调用
operator new(size_t)或operator new[] (size_t)申请空间 - 进行强制类型转换
1 | // ====== 申请单个空间 ====== |
static_cast
是一种用于显示类型转换的强制转换运算符。是一个编译时运算符,用于在兼容类型之间执行转换.
1 | static_cast<new_type>(expression) |
new 在申请 object 空间时,主要会经历三个过程:
1.调用 operator new(size_t) 或 operator new[] (size_t) 申请空间
2.进行强制类型转换
3.调用类的构造函数
1 | // ====== 申请单个object ====== |
注意:
申请的空间大小为
1 | sizeof(classname)*N+4; |
前4个字节写入数组大小,最后调用N次构造函数。
这里为什么要写入数组大小呢?释放内存之前会调用每个对象的析构函数。但是编译器并不知道 q 实际所指对象的大小。如果没有储存数组大小,编译器如何知道该把p所指的内存分为几次来调用析构函数呢?
new[] 调用的是operator new[],计算出数组总大小之后调用operator new。值得一提的是,可以通过()初始化数组为零值,实例:
1 | char* p = new char[32](); |
等同于:
1 | char *p = new char[32]; |
new的底层
operator new(size_t) 是系统提供的全局函数,其 底层是由 malloc 实现的:
1 | /* |
delete
delete 的过程与 new 很相似,会调用 operator delete(void*) 或 operator delete[] (void*) 释放内存。
1 | delete p; |
delete 释放 object 空间
- 调用类的析构函数
- 调用
operator delete(void*)或operator delete[] (void*)释放内存
1 | delete obj; |
new[]分配的内存只能由delete[]释放。如果由delete释放会崩溃,假设指针obj1指向new[]分配的内存,因为要4字节存储数组大小,实际分配的内存地址为obj1-4,系统记录的也是这个地址。delete[] 实际释放的就是obj1-4指向的内存。而delete会直接释放obj1指向的内存,这个内存根本没有被系统记录,所以会崩溃。
delete底层
1 | /* |
总结:
在new一个数组时,与malloc相似,os会维护一张记录数组头指针和数组长度的表.
释放基本数据类型的指针时,数组的头指针最终会被free(q)释放,所以调用delete q或者delete[] q,最终的结果都是调用free(q);
释放自定义类型的数组时,如果类中有需要在析构函数中释放,直接调用delete obj只会调用一次析构函数,后执行free(),就没有调用其他的析构函数,会造成内存泄漏.一定调用delete[] obj释放内存。
new 和 delete的重写和重载
这两个函数在使用时,其实执行的是全局的::operator new和::operator delete,如果我们在类中重载了这两个函数,那么就会调用我们实现的,没有则使用全局的. 对于基本数据类型则使用全局的.
1 | //全局运算符定义格式 |
标准库提供的全局的 operator new( 函数 ) 有六种重载形式,operator delete也有六种重载形式:
1 | // =======================new=========================// |
new/delete运算符重载规则:
- new和delete运算符重载必须成对出现。
- new运算符的第一个参数必须是size_t类型的,也就是指定分配内存的size尺寸;delete运算符的第一个参数必须是要销毁释放的内存对象。其他参数可以任意定义。
- 系统默认实现了new/delete、new[]/delete[]、 placement new 5个运算符(expression)。它们都有特定的意义。使用它们在底层调用对应的函数,可以是系统提供的,也可能是被重写或重载的。
- 你可以重写默认实现的全局运算符,比如你想对内存的分配策略进行自定义管理或者你想监测堆内存的分配情况或者你想做堆内存的内存泄露监控等。但是你重写的全局运算符一定要满足默认的规则定义。也可以重载全局运算符,但也必须符合默认的规则,即第一个参数不能变。
- 如果你想对某个类的堆内存分配的对象做特殊处理,那么你可以重载这个类的new/delete运算符。当重载这两个运算符时虽然没有带static属性,但是不管如何对类的new/delete运算符的重载总是被认为是静态成员函数。
- 当delete运算符的参数>=2个时,就需要自己负责对象析构函数的调用,并且以运算符函数的形式来调用delete运算符。
- 这里的重载遵循作用域覆盖原则,即在里向外寻找operator new的重载时,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。比如如果这里只将Foo中
operator new(size_t, const std::nothrow_t&)删除掉,就会在61行报错:
对象的自动删除
一般来说系统对new/delete的默认实现就能满足我们的需求,我们不需要再去重载这两个运算符。那为什么C++还提供对这两个运算符的重载支持呢?答案还是在运算符本身具有的缺陷所致。我们知道用new关键字来创建堆内存对象是分为了2步:1.是堆内存分配,2.是对象构造函数的调用。而这两步中的任何一步都有可能会产生异常。如果说是在第一步出现了问题导致内存分配失败则不会调用构造函数,这是没有问题的。如果说是在第二步构造函数执行过程中出现了异常而导致无法正常构造完成,那么就应该要将第一步中所分配的堆内存进行销毁。C++中规定如果一个对象无法完全构造那么这个对象将是一个无效对象,也不会调用析构函数。为了保证对象的完整性,当通过new分配的堆内存对象在构造函数执行过程中出现异常时就会停止构造函数的执行并且自动调用对应的delete运算符来对已经分配的堆内存执行销毁处理,这就是所谓的对象的自动删除技术。正是因为有了对象的自动删除技术才能解决对象构造不完整时会造成内存泄露的问题。
全局delete运算符函数所支持的对象的自动删除技术虽然能解决对象本身的内存泄露问题,但是却不能解决对象构造函数内部的数据成员的内存分配泄露问题,此时我们需要将析构函数中需要释放的数据成员的内存在重载的operator delete函数中进行释放。
operator new运用技巧
- 用于调试
- 内存池优化
- STL中的new
new和malloc的区别
内存的申请所在位置
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
特别的,new甚至可以不为对象分配内存!定位new的功能可以办到这一点:
1 | new(place_address) type |
place_address为一个指针,代表一块内存的地址。
返回类型安全性
new返回对象类型的指针,类型严格匹配
而malloc返回void *,需要通过强制类型转换将void *转换成我们需要的类型.
内存失败时的返回值
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
是否需要指定内存大小
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。
是否调用构造函数/析构函数
使用new操作符来分配对象内存时会经历三个步骤:
- 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
- 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
- 第三部:对象构造完成后,返回一个指向该对象的指针。
使用delete操作符来释放对象内存时会经历两个步骤:
- 第一步:调用对象的析构函数。
- 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。
对数组的处理
C++提供了new[]与delete[]来专门处理数组类型,new[]分配的内存必须使用delete[]进行释放。
new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会找出数组对象部分释放的现象,造成内存泄漏。
至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:
1 | int * ptr = (int *) malloc( sizeof(int)* 10 ); //分配一个10个int元素的数组 |
new与malloc是否可以相互调用
operator new 的实现可以基于malloc,而malloc的实现不可以去调用new。
是否可以被重载
opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:
// 这些版本可能抛出异常
1 | void * operator new(size_t); |
1 | // 这些版本承诺不抛出异常 |
1 | // VS C++下还有两个,GCC下不确定 |
我们可以重载上面函数版本中的任意一个,前提是自定义版本必须位于全局作用域或者类作用域中。总之,我们有足够的自由去重载operator new /operator delete ,以决定我们的new与delete如何为对象分配内存,如何回收对象。
而malloc/free并不允许重载。
能够直观地重新分配内存
使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。
客户处理内存分配不足
在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new_handler。new_handler是一个指针类型:
1 | namespace std |
指向了一个没有参数没有返回值的函数,即为错误处理函数。为了指定错误处理函数,客户需要调用set_new_handler,这是一个声明于的一个标准库函数:
1 | namespace std |
set_new_handler的参数为new_handler指针,指向了operator new 无法分配足够内存时该调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上就要发生替换)的那个new_handler函数。
对于malloc,客户并不能够去编程决定内存不足以分配时要干什么事,只能看着malloc返回NULL。
Saturday, 2 March 2024
KDE got accepted as a mentoring organization for Google Summer of Code 2024! Are you thinking about getting involved into KDE development? Check out the cool ideas KDE devs came up, they showcase what can be achieved by taking part as a student in GSoC. How to start? How to get involved? How to make an impression that will help your application?
Prerequisites
- You like KDE, you like us as a community, you can follow our philosophy, you like our product (a desktop or at least an specific application), and you resonate with our tech stack (C++, Qt, CMake).
- Grab some code from our GitLab, clone a repository and build it locally. This sounds easy. For first-timers it is not easy. Reach out for help in case you struggle.
- Run your self-built software. Now you can explore the joy of developing KDE.
You do not need an idea, at least not yet. Give it some time.
Get involved
Try getting involved. Usually it is not easy to fix bugs of implement a feature request from KDE bugtracking system. Some are hard to fix. Others need debates of future directions or best ways to get things done.
I propose you start looking for other opportunities:
- Fix compiler warnings. Compilers analyze the code and as a result they might warn you. This can have various reasons like bad coding practice, code that is difficult to read and might easily be misread by humans, code with bad performance, bug-prone constructs.
- Fix deprecation warnings. KDE, Qt, and every software evolves. Old interfaces are replaced by newer ones. The old ones are not directly thrown away, but deprecated. The deprecation warning reminds the developer to migrate from the old to the new interface. Some deprecations are trivial to fix, others require ample code changes.
- Fix findings of static analyzers and linters. These are tools that analyze code more thorough compared to a compiler for the price of a longer runtime. They offer great hints for but are prone to false-positives (wrong warnings). Good tools for KDE are Cppcheck, Clazy and qmllint.
- Fix crash bugs. Crashes often occur when the code contains memory issues. Examples are using objects that were deleted, accessing arrays out of bounds, de-referencing null pointers. Tools like a good debugger, Valgrind, and AddressSanitizer (and its cousins MemorySanitizer and UndefinedBehaviorSanitizer) help to localize the problem. Crashes are more difficult to understand and fix compared to warnings for tools.
Try to work for fixes of one to ten cases. More makes reviewing harder. Create a pull request and wait for feedback.
Rationale
Why do I think these areas are good to start working? The maintainer might reject your pull request. This can always happen. Compared to implementing a whole new feature, the amount of work you invested is limited.
Once you have an idea for GSoC and write your application, you can point to your pull requests as proof of work. Maintainers see your involvement, they see how you interact, and you get an early sense whether you like the contributing experience or not.
Even in the case that you do not want to become an GSoC student, you improved KDE a tiny little bit. Great feeling, isn't it?
Word of warning
Not everybody deem warnings worth to be fixed -- in general or in specific cases. You will learn what kind of warnings getting fixed are welcome. I already wrote a blog post about my experiences with fixing some Cppcheck in Kile.
Thursday, 29 February 2024
It’s February already, and as expected I didn’t have too much time on my hands this month. I have some exciting related news though:
KDE Megarelease 6
If you somehow haven’t heard, a bunch of KDE 6-related stuff released yesterday! I highly recommend looking at the very nice announcement Carl and the rest of the KDE Promo team put together, it looks so good. Some of my changes in KCMs, Kiten, UnifiedPush support in Tokodon & NeoChat, PlasmaTube, and Tokodon are showcased there 🤩 One of my favorite changes is probably in KWin and the Wayland session, it’s near-perfect on my machine now.
NLnet Grant
For a while now, people have been asking about funding work on important Wayland features for artists and people who depend on accessibility features. We have been turning down offers from individual fundraisers, because behind the scenes my employer arranged a grant us to work on this task from NLnet! “Us” here means me and Nicolas Fella. I hope to begin in early April.
Tokodon
[Feature] I changed the thread visuals to improve the readability of replies. Note that it’s only one level deep, but even with that limitation I find it to be way better than it was before! [24.05]

[Feature] I did some neat notification changes, such as enabling group notifications by default and hiding the post actions unless required for notifications. The notifications page should have less visual noise now. [24.02.1]

[Feature] Tokodon now warns if replies may be hidden from your server and suggests to open the original post in your web browser. This isn’t the greatest solution yet, eventually I want a way to load the post on the original server within Tokodon. This is a good start though, instead of showing nothing. [24.05]

NeoChat
[Bugfix] Rebased and cleaned up my bugfix to prevent editing messages destroying the formatting. This should be integrated soon!
Frameworks
[Bugfix] Fixed my typo that caused KFileMetadata to fail when not building with KArchive support, oops! [6.1]
[Bugfix] Add the missing and new Breeze icons dependency to kiconthemes. [6.1]


