【C++学习】string的模拟实现

news2024/11/26 20:23:31

🐱作者:一只大喵咪1201
🐱专栏:《C++学习》
🔥格言:你只管努力,剩下的交给时间!
图

上篇文章中本喵介绍了C++标准库中string类的使用,下面本喵来模拟实现一下string类。库中的string类是将模板实例化并且typedef后得到的类,所以我们直接实现类,而忽略模板。

string类的模拟实现

  • 🍚默认成员函数
    • 🍛构造函数
    • 🍛拷贝构造函数
    • 🍛赋值运算符重载
    • 🍛析构函数
  • 🍚常用的string接口
    • 🍛[]操作符重载
    • 🍛迭代器iterator
    • 🍛size()和capacity()
  • 🍚增
    • 🍛改变容量
    • 🍛插入
    • 🍛删除
    • 🍛查找
  • 🍚流插入(<<)流提取(>>)运算符重载
  • 🍚总结

🍚默认成员函数

首先就是来实现类和对象中的四大默认成员函数,构造函数,拷贝构造函数,析构函数,赋值运算符重载函数。

🍛构造函数

		//使用C字符串构造
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

			strcpy(_str, str);
		}

这是使用C字符串进行的构造。

  • 使用C字符串进行构造的时候,需要加一个缺省值,当创建一个空的string的时候就会使用到这个缺省值。
  • _size表示的是有效字符的个数,不包括’\0’,是通过C语言中的strlen函数求出来的。
  • _capacity表示的是有效空间的大小,同样不包括’\0’所占的空间,在最初构造一个string对象的时候,让_size和_capacity的大小一样。
  • 使用new开辟动态空间来存放字符串的时候,开辟的空间需要比_capacity大一个,这多出来的一个是专门用来存放’\0’的,一个字符串中只有一个’\0’。

为了查看效果,需要实现一个打印字符串的成员函数:

		//字符串打印
		const char* c_str()
		{
			return _str;
		}

tu
可以看到,使用C字符串,空的string,以及使用string类都可以实现一个新的string类对象的创建。

🍛拷贝构造函数

编译器自动生成的默认拷贝构造函数进行的浅拷贝,如下图:

图
当使用s1来拷贝构造s2的时候,就会导致如上图所示的问题,新的s2指向的动态空间和s1指向的动态空间是同一块空间,这样不仅在释放的时候会导致错误,而且在使用的时候也会在改变s2中的内容的同时将s1中的内容改变,所以对于拷贝构造函数需要进行深拷贝。

所谓深拷贝就是将内容全部复制过来,但是s1和s2使用的是两块空间:
图
而深拷贝的实现方式又有俩种,分别是传统方式和现代方式。

传统方式实现:

		//传统拷贝构造函数
		string(const string& s)
		{
			_size = s._size;
			_capacity = s._capacity;
			_str = new char[_capacity + 1];

			strcpy(_str, s._str);
		}

这种方式在新的string对象创建的时候新开辟了一个和原对象一样大小的空间,并且将原对象中的字符串拷贝过来。

现代方式实现:

		//现代拷贝构造函数
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}

上面的便是现代版本的拷贝构造函数实现的思想,下面本喵用图来给大家解释:
图

tmp通过s1中的字符串构造了一个新的string类对象,而此时s2的_str指向的空间中是随机值。
为了让s2中的内容变成和s1中的一样,只要将临时的类对象tmp中内容拿过来即可。

图
此时就将tmp和s2中的内容全部进行了交换,而s2也没有开辟新的空间就实现了拷贝构造的目的。

注意:

图
红色框中的初始化列表中,必须将s2的_str置为空,否则会编译报错。

  • s2拿走了tmp中的_str,而将自己的_str给了tmp,当tmp生命周期结束的时候,会释放空间,如果不是nullptr就会释放原本不属于tmp而属于s2的空间,所以就会报错.。

swap函数的实现:

上面使用的swap是标准算法库中的swap函数,但是在string标准库中还有一个swap函数:

图

这俩个函数是有区别的。

  • 标准算法库中的swap是一个函数模板,它会自动推演数据类型,如果使用这个函数进行俩个string类对象的交换,会发生三次拷贝构造,系统开销比较大。
  • 所以在交换string类对象的时候使用string库中的swap函数比较合适。

既然是string的模拟实现,所以同样也需要我们自己实现一个。

		//交换函数
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

在string中的swap同样是通过标准算法库中的swap实现的,细心的小伙伴肯定发现了一个问题,在上面代码中,使用标准算法库中的swap交换的数据都是内置类型的,而函数模板中有一个语句c(a),也就是用a来拷贝构造c。

如果a和c都是自定义类型我们不难理解,但是此时的a和c都是内置类型。

说明:

内置类型也有拷贝构造函数以及析构函数,一般情况下是不用考虑这个的,而且在C语言中是没有的,但是为了迎合模板,就不得不有。

图

可以看到,内置类型也可以使用拷贝构造以及赋值运算符重载等方式来创建。

此时现代方式的拷贝构造函数就有了更加简洁的写法:

		string(string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}

此时就非常简洁的实现了深度拷贝。

🍛赋值运算符重载

和拷贝构造函数一样,如果使用编译器自动生成的默认赋值运算符重载函数,也是会有浅拷贝的问题,所以赋值也是需要深度拷贝的。同样的,也是有传统和现代俩种实现方式。

传统方式:

		//传统赋值运算符重载函数
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				_size = s._size;
				_capacity = s._capacity;

				delete[] _str;

				_str = new char[_capacity + 1];

				strcpy(_str, s._str);
			}
			return *this;
		}

和拷贝构造函数一样,也开辟一个和原本类对象一样大小的空间,然后将内容赋值过来。

  • 需要判断一下是否是自己给自己赋值,如果是的话就直接返回this指向的内容。
  • 给类对象赋值以后,这个类对象原本指向的空间不再使用了,因为开辟了新的空间,所以要将原本指向的空间使用delete释放掉。

现代方式:

和拷贝构造函数现代方式实现的思路一样,也是拿其他对象的构造成果,坚决不自己开辟空间。

		//现代赋值运算符重载函数
		string& operator=(string& s)
		{
			if (this != &s)
			{
				//string tmp(s._str);
				string tmp(s);
				swap(tmp);
			}
			return *this;
		}

使用s拷贝构造一个临时对象tmp,然后将tmp中的内容拿走,最后返回this指针的内容。

既然都是找一个中间的打工仔,而不自己实现,可以直接使用形参这个打工仔:

		string& operator=(string s)
		{
			swap(s);
			return *this;
		}

此时就更加的简洁,而且形参的地址必定和this中的值不同,所以都不用排除自己给自己赋值的情况。

  • 现代版本的深度拷贝仅是为了代码更加简洁而不考虑效率。

🍛析构函数

析构函数没有什么要点,非常的简单:

		//析构函数
		~string()
		{
			delete[] _str;
			_size = 0;
			_capacity = 0;
		}

直接使用delete释放空间即可。

🍚常用的string接口

🍛[]操作符重载

在使用string的时候,可以想方法C字符串数组一样使用[]来访问string类对象中的字符,下面本喵来给大家看看它是如何实现的:

		//[]重载
		//可读可写
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		//可读不可写
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

操作符[]的重载有俩个重载函数,一个是可读可写的,一个是只读不可写的。

图
可以看到,成果的将s1中字符串的前5个字符的ASCII码都加了1。

🍛迭代器iterator

我们指定,迭代器是行为上像指针一样的东西,在string类中,迭代器就是一个指针:

typedef char* iterator;

此时一个迭代器类型便实现好了,有了迭代器后就需要有对应的接口来配合迭代器使用。

		//begin()
		iterator begin()
		{
			return _str;
		}

		//end()
		iterator end()
		{
			return _str + _size;
		}

此时俩个和迭代器最常用的接口便实现好了。

图
通过迭代器,将s1中的内容全部打印了出来。

范围for:

范围for一直给我们的映像都很神奇,现在可以揭下它的神秘面纱了。

图

此时范围for是正常的。

图
仅仅将begin函数中的b换成大写的B。

图
在执行的时候报了一大堆的错误,都是和begin有关的。

图

  • 在编译的时候,编译器会将范围for的代码转换成使用迭代器的代码,然后再进行编译。
  • 此时begin函数没有了,所以使用迭代器的方式就无法实现了,所以就会报错。

编译器是不认识范围for这个语法的,所以它必须进行转换以后编译器才能够认识,而且这个转换是写死了的,必须使用迭代器iterator,begin,end函数,少一个都不行。

🍛size()和capacity()

成员函数size()是用来获取效字符的个数的,capacity()是用来获取有效空间的大小的。

		//size
		size_t size() const
		{
			return _size;
		}
		//capacity()
		size_t capacity() const
		{
			return _capacity;
		}

🍚增

🍛改变容量

  1. reserve()
		//reserve()
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

该函数的作用就是在扩容,和C语言中的realloc的作用相似,但是C++中并没有realloc这样的函数,所以扩容就需要我们手动扩容,就在新开辟一块空间,并且将旧空间中的内容拷贝进去,再将旧空间释放掉。

  1. resize()
		//resize()
		void reszie(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);
				for (int i = _size; i < n; ++i)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

resize分三种情况:

  • n小于等于原本的_size,此时是在缩容,只需要将_size的值改为n,并且将下标为n的位置处放一个’\0’。
  • n大于原本的_size,并小于原本的_capacity,此时不需要进行扩容只需要将原本的_size设置成n即可,并且将相比于原本字符多出来的位置用形参ch来填充,如果没有传ch,就使用缺省值’\0’。
  • n大于原本的_capacity,此时需要进行扩容,之后的操作和上面的一样。

图
运行结果符号预期。

🍛插入

  1. push_back()
		//push_back()
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size++] = ch;
			_str[_size] = '\0';
		}

尾插插入的只有一个字符串,而且是在字符串的末尾插入,在插入之前需要判断容量是否够用,如果不够则需要扩容。

  • 当原本的容量就是0的时候,就不能简单二倍扩容,因为0乘2以后还是0,就需要给定一个初始容量,这里本喵给的初始容量是4。
  1. append()
		//append()
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

尾部插入字符串。同样需要判断是否需要扩容,之后在原本的’\0’位置处开始,放入插入的字符串,因为插入的字符串自带’\0’,所以插入以后不需要自己再写’\0’。

  1. +=
		//+=一个字符
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		//+=字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

最常使用的就是+=,因为它的可读性高,而它实现的原理就是在运算符重载的时候复用了push_back和append函数。

图
+=一个字符和一个字符串都实现了。

  1. insert
  • 在指定位置插入一个字符
    tu
    挪动数据的时候有俩中方式,如上图中的红色框。
  • 第一个红色框中的方法,如果不将pos强转为int类型的话,当在0位置插入字符的时候,就会在while的条件判断时发生隐式类型转换,int类型的end提升成size_t,此时就不会出现负数,就会造成死循环。
  • 在指定位置插入一个字符串

图
在挪动数据的时候同样有俩种方式,其中第一种和上面的插入一个字符时的注意点一样。

下面本喵来画图分析一下,插入的过程:

图

  • end就等于_size+len,从下标为end处开始,将end-len位置的元素移动过去,直到将pos位置处的元素也移动走。
  • 这个过程中会有元素的覆盖,但是是使用前面的元素覆盖后面的元素,所以不存在问题。

图

  • 移动完数据以后,将要插入的字符串,除’\0’以外的所有字符复制到pos位置开始的空间中,如上图中黄色线。

图
和我们分析的结果一致。

🍛删除

  1. erase()
    tu
    在这里我们定义一下npos,这是一个静态成员变量,在类中进行声明的时候,给它一个缺省值-1,在定义的时候就会使用到这个缺省值。
  • 静态成员变量的声明在类中,定义必须在类外,但是有一个特殊类型,整型家族的静态成员就可以在声明的时候给一个缺省值。
  • 由于是size_t类型的-1,所以它的实际大小就是32个1的二进制,转换成十进制大致是42亿9千万。

npos也代表值string类对象字符串的末尾。

		//erase
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

如果要删除的字符串长度已经超出了现有字符串的长度,那么就从指定位置开始全部删除完,否则就需要将对应位置以后相应长度的字符串删除后,再将剩下的字符移动到前面。

  1. clear()
		//clear()
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}

clear是情况所有字符串,但是不改变容量。

图
对应的结果是符号我们的预期的。

🍛查找

		//find()
		//查找一个字符
		size_t find(char ch, size_t pos = 0) const
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				pos++;
			}
			return npos;
		}

		//查找一个字符串
		size_t find(const char* str, size_t pos = 0) const
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

这是查找一个字符和一个字符串的代码,找到了就返回下标,找不到就返回npos的值,在查找字符串的时候,使用了C语言中的strstr查找字串的函数。

图
结果也符合我们的预期。

🍚流插入(<<)流提取(>>)运算符重载

在前面本喵已经实现过一次了,这里再提及一下。

  1. 流插入(<<)运算符重载
ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}
		return out;
	}
  1. 流提取(>>)运算符重载
istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}
  • cin和scanf一样,当遇到空格或者换行符时,自动将字符串分割
  • 如果输入的内容很多的话,就会不停地+=,也会导致不停扩容,导致系统开销较大。

改进:

istream& operator>>(istream& in, string& s)
{
    s.clear();
    char buff[128] = { '\0' };
    size_t i = 0;
    char ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
        if (i == 127)
        {
            s += buff;
            i = 0;
        }
        buff[i++] = ch;
        ch = in.get();
    }
    if (i >= 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

先开辟一个数组,将输入的字符放在数组中,当数组满了后进行一次扩容,此时即使输入很长的字符串,扩容的频率也不会很高。

🍚总结

我们自己模拟实现的string并不是要和标准库中的完全一样,只是为了了解string的底层原理,让我们在使用起来更加得心应手。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Spring框架】爆gan两万六千字,助你通关IoC和DI

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;【Spring】 &#x1f96d;本文内…

navicat连接mysql数据库

一、打开navicat软件 二、创建一个测试连接 1、点击【连接】&#xff0c;选择【MySQL】 2、创建连接。 3、连接出现报错 三、解决方式&#xff1a; 1、键盘上wins r 同时按&#xff0c;输入cmd&#xff0c;调出命令行窗口。 2、通过cmd登陆mysql 3、输入以下语句修改密码 更…

拿下50亿后,岚图能否一绘蓝图?

近两年&#xff0c;我国新能源汽车市场呈现出一派百家争鸣、高歌猛进的势头。2021年&#xff0c;A股新能源汽车指数全年上涨42.72%&#xff0c;大幅跑赢沪深300、中证500和汽车指数。 然而进入2022年寒冬&#xff0c;新能源汽车市场却集体打了个“哆嗦”。 美股特斯拉自今年4…

设备安装CoreELEC系统,并配置遥控:实现低成本NAS影音播放器

目录0. 前言CoreELEC简介动机硬件1.准备工作1.1下载镜像1.2 制作启动U盘2.安装CoreELEC2.1 从U盘启动2.2 CoreELEC写入盒子emmc3.设置遥控器本文原首发于zdm&#xff0c;由于该平台审核机制出现问题且编辑器极其不好用&#xff0c;所以发布于此 仅作为记录操作的用途 0. 前言 …

代码随想录66——额外题目【回溯、贪心】:52N皇后II、649Dota2 参议院、1221分割平衡字符串

文章目录1.52N皇后II1.1.题目1.2.解答2.649Dota2 参议院2.1.题目2.2.解答3.1221分割平衡字符串3.1.题目3.2.解答1.52N皇后II 参考&#xff1a;代码随想录&#xff0c;52N皇后II&#xff1b;力扣题目链接 1.1.题目 1.2.解答 这道题和之前做过的 51.N皇后 是一模一样的&#x…

怿星科技参加2022(第六届)高工智能汽车年会

2022&#xff08;第六届&#xff09;高工智能汽车年会将于下周三在上海虹桥拉开帷幕&#xff0c;怿星科技作为本次活动的赞助商&#xff0c;将在11月30日下午的【座舱算力与系统】分论坛与大家分享关于智能汽车软硬分离探索与实践的专题演讲。此外&#xff0c;在活动期间&#…

docker 之间相互通讯

方式一&#xff1a;通过IP直接访问(不推荐) 查询容器对应的IP命令&#xff1a; docker inspect 容器 | grep IPAddress 通过docker容器启动的实例分配的ip地址&#xff0c;直接访问&#xff0c;docker重启时IP会发生变化&#xff0c;所以不推荐 方式二&#xff1a;通过端口…

html 多按钮点击按钮颜色改变 捷弘和宇乐两个按钮,在点击选中时颜色做区分

捷弘和宇乐两个按钮&#xff0c;在点击选中时颜色做区分 点击捷弘按钮&#xff0c;捷弘按钮颜色改变&#xff1b;点击宇乐按钮&#xff0c;宇乐按钮颜色改变&#xff0c;捷弘按钮颜色恢复 如图&#xff1a; html代码&#xff1a; <a id"jiehong" class"bt…

安泰测试-Keithley吉时利2461数字源表产品参数

Keithley SMU 2461,吉时利2461数字源表&#xff1a; Keithley SMU 2400 图形系列 SourceMeter 溯源、测量、绘图和分析功能触手可及 Keithley 的触摸屏图形源测量单元仪器可带来直观的测试平台体验&#xff0c;满足电源和测量需求。 应用&#xff1a;离散设备、组件、FET、二管…

cpu设计和实现(pc跳转和延迟槽)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 cpu按部就班地去取指执行是理想情况。很多时候&#xff0c;cpu的pc寄存器会跳来跳去的。跳转的情况很多&#xff0c;一般可以分成三种。第一&#…

CRM软件哪个好?国内外6大顶级CRM软件盘点

CRM软件哪个好&#xff1f;国内外8大顶级CRM软件盘点 市场上存在上千种CRM管理系统&#xff0c;他们各有特色&#xff0c;难免让企业在选型时遇到诸多困难&#xff0c;有人说这款好用&#xff0c;有人说哪款好用... 所以本文将整理国内外那些顶级的CRM软件。 一、国内外6款CR…

分省/市/县最低工资标准-12-2021年1949-2020全国/省/市/县GDP数据

一、最低工资数据 1、数据来源&#xff1a;各省\市\县政府公布资料 2、时间跨度&#xff1a;2012-2021年 3、区域范围&#xff1a;全国各省\市\县 4、指标说明&#xff1a; 部分数据如下&#xff1a; 二、各省市县人均GDP 1、数据来源&#xff1a;地方统计局 2、时间跨度…

客户CRM能给企业带来哪些用处?

一、民营企业增加更多的总收入 CRM控制系统透过软件系统数个沟通沟通交流平台&#xff0c;不断扩大了与客人的沟通沟通交流距&#xff0c;保有终端版CRM&#xff0c;产品销售项目组足不出户与客人沟通交流&#xff0c;给民营企业增添了极高的股权投资股权投资回报。 简道云CR…

centos7中sshd -t没内容输出日志也没内容但sshd服务重启一直失败解决方法、strace命令的使用以及使用场景说明

文章目录ssh服务启动报错问题sshd启动报错说明解决方法常规排除定位法解决修改ssh的selinux上下文扩展知识【strace命令】ssh服务启动报错问题 sshd启动报错说明 sshd服务如果起不来&#xff0c;查日志一般都会有相应信息记录&#xff0c;如果sshd -t中有输出&#xff0c;不会…

都要2023年了,Android开发是否还值得入场?

随着手机行业的飞速发展&#xff0c;现在国产手机也迎来了高速发展时期&#xff0c;越来越多的人使用国产手机&#xff0c;同时开发安卓APP的人也越来越多了&#xff0c;下面来看看安卓app开发市场前景如何&#xff1f; 1、 消费用户群体成熟。安卓智能手机的市场份额可以说是庞…

Linux进程通信:命名管道,System V共享内存

目录 1.命名管道 2.共享内存 3.共享内存和管道的生命周期 4.共享内存的优缺点 1.命名管道 命名管道和匿名管道的最大差别是&#xff1a;命名管道是创建在磁盘里的一个有名字的文件。这个文件不存实际的数据。但是不同进程可以通过文件路径找到相同的struct file&#xff08…

你好,法语!A2知识点总结(2)

2.各种词类 2.1代词 代词&#xff0c;形容词&#xff0c;副词&#xff0c;介词 重读人称代词 1&#xff09;构成 2&#xff09;作用 1-重读人称代词≧2 一般次序&#xff1a;“他/她/它、你、我” Ex: Elle, toi et moi, nous allons faire du shopping. 2-soi: 泛指 Ex: -…

网络管理中TRUNK的作用和使用

作者简介&#xff1a;一名99年软件运维应届毕业生&#xff0c;正在自学云计算课程。宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。创作不易&#xff0c;动动小手…

路径规划算法之几何建模

目录 1 几何建模简介 1.1 机器人建模 1.2 环境建模 2 多边形和多面体模型 2.1 凸集的定义 2.2 凸集的边界表示与实心表示 2.3 非凸多边形 2.4 逻辑谓词 2.5 多面体模型 2.6 阿拉伯数字半代数模型 2.7 非凸多边形的另一种编码 2.8 3D三角形 2.9 非均匀有理B样条曲线…

Qt Quick、QML01——QML内容结构介绍

目录标题一、从Window 窗口组件开始&#xff08;一&#xff09;属性flags 表&#xff1a;visibility 表&#xff08;二&#xff09;信号和处理器&#xff08;槽函数&#xff09;&#xff08;通用&#xff09;&#xff08;三&#xff09;调用函数&#xff08;通用&#xff09;&a…