Learning C++ No.11【string类实现】

news2024/12/24 0:05:53

引言:

北京时间:2023/2/19/8:48,昨天更新了有关进程状态的博客,然后在休息的时候,打开了腾讯视屏,然后看到了了一个电视剧,导致上头,从晚上6点看到了10点,把我宝贵的博客时间给搞没了,伤心,现在就让我们开始将功补过,把昨天就应该开始写的博客给补一补!加油,不怂,就是干!今天博客的内容非常简单,就是使用我们以前学过的知识,无论是类和对象、内存管理,还是string类中的函数使用,运用它们写一个较为完整的string类出来,也就是自己实现string类,写库里面的东西,想想还是挺激动的,就让我们带着这份激动,go go go!
在这里插入图片描述

string类的模拟实现

string类基本框架

首先开始学习之前,明白string类的底层就是一个字符数组而已,本质上我们在实现string类的过程中,也就是在补全之前学习中的一些不足,和一些重难点,当然最关键的可以复习之前的各种知识点,并且在复习的过程中更好的理解这些知识点的使用,把一小块一小块的知识给合并成一大块的知识,所以模拟实现string类是非常重要的。

步入正题,实现string类基本框架,如下代码:从代码入手,再做细节方面处理
注意图中小写s应该替换成大写
以上就是一个string类的基本框架,大部分函数功能还没有实现,只是实现了基本的构造函数和析构函数以及一些细节方面的处理。

string类框架之后的地基

搞定了上述有关string类中的构造函数和析构函数,当然最重要的是类和对象的使用,我们可以说是有了一个string类的基本框架,也可以说是我们拥有了一个基本的类框架,所以现在就让我们走进string类,复习并使用更多的知识来实现这个STL中的经典字符类。

拷贝构造

在搞定了构造函数和析构函数,之后实现一个类最重要的莫过于是拷贝构造函数了,虽然拷贝构造函数和构造函数、析构函数一样,都是一个默认成员函数,但是懂的都懂,默认成员函数并不是万能的,准确的来说是编译器并不是万能的,这些默认成员函数大部分都只是对内置类型起作用,而我们自己实现的自定义类型就像是后娘养的,人家是爱答不理,并不能很好的把自定义类型进行相应的初始化,所以对这些后娘养的自定义类型,则需要我们自己来实现初始化,当然经典的初始化场景有在初始化列表初始化、给缺省值(但本质还是在初始化列表初始化)、调用拷贝构造函数初始化等,所以调用拷贝构造函数初始化是自定义类型初始化的一个好地方,我们现在就来复习巩固一下拷贝构造函数。(当然重点就是想要讲深浅拷贝问题)
如下图:
在这里插入图片描述
当然上述的前提是通过成员变量中有一个char* _str的指针,涉及指针就涉及指针指向的空间,就涉及深拷贝问题,涉及深拷贝就涉及析构问题,这些看似无关,却紧密相连,这就是自我实现string类的好处,搞清各个知识点之间的关系和熟练掌握运用,所以以后写拷贝构造函数第一点就是考虑深浅拷贝问题。

const成员函数使用场景和运算符重载

在这里插入图片描述从上图中我们可以发现,我们许多函数在实现的时候,都可以去调用那些已经自己实现好了的函数,或者库函数来实现一些新的功能,并且可以发现,只要使用const来修饰成员函数,只要我们对该函数不做改变数据的操作,这种方法是很好的,可以有效的避免权利放大问题,解决不必要的麻烦,可以让我们使用const对象调用函数的时候,变得更加的放心,程序变得更加的稳定。 并且注意: 我们在进行运算符重载时,进行字符串的比较,使用的是strcmp函数,表明,此时我们比较的是该字符串的ASCII码值,而不是该字符串的大小和容量。

string类中迭代器的实现

在这里插入图片描述
从上图可以看出,范围for的本质就是迭代器,从迭代器可以实现语法糖来分析,足以看出迭代器身为STL六大天王之一不是浪得虚名的。并且此时注意: const修饰的迭代器,该迭代器对象是可以被改变的,只是该对象中指向的内容不可以被修改而已

扩容函数和字符、字符串插入函数

在这里插入图片描述

总了来说,字符和字符串插入删除函数大致上都是差不多的,细节方面处理到位就行,跟数据结构中的顺序表本质上是一样的,这里就不过多介绍。并且此时我们把字符插入这些函数实现之后,string类中的函数,也就完成了地基部分,此时我们完成了地基就可以开始盖房子了,由于时间关系,我们把盖房子部分留到下一篇博客。

具体代码如下: 包括测试部分(注释很全)

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<assert.h>
using namespace std;

namespace wwx
{
	class String
	{
	public:
		typedef char* iterator;//普通类型迭代器
		typedef const char* const_iterator;//const类型迭代器(注意此时自己是可以修改的,只是解引用后的值是不可以修改的)
		iterator begin()
		{
			return _str;//此时因为String本质上就是一个字符数组,所以_str就是首元素地址,就是第一个字符
		}
		iterator end()
		{
			return _str + _size;//这个就是最后一个字符(前提是要知道第一个字符的位置)
		}
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}
		String(const char* str = " ")//或者写成"\0",反正这个位置只要可以让strlen算出一个0来就行了(全缺省构造函数)
			: _size(strlen(str))
		{
			_capacity = _size == 0 ? 3 : _size;//_capacity为0第一种解决方法
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		String(const String& s)//注意:拷贝构造也是有初始化列表的(并且要回想起以前有关this指针的知识,此时*this就是s3,str就是s2)
			:_size(s._size)
			,_capacity(s._capacity)
		{
			//深拷贝(因为使用了指针,或者说因为有自己实现析构函数)
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
		}//并且此时要记住,此时的拷贝构造除了利用*this指针以外,还有一个是使用赋值运算符(=)
		String& operator=(const String& s)//区分赋值和拷贝构造,赋值是两个已经存在的对象,而拷贝构造是一个已经初始化的对象去初始化另一个要创建的对象
		{
			if (this != &s)//防止自己给自己赋值
			{
				//_size = s._size;
				//_capacity = s._capacity;
				//delete[]_str;//这种赋值方法,可以很好的避免被赋值空间太大或太小的问题,只是伴随着开空间的消耗而已
				//_str = new char[s._capacity + 1];
				//strcpy(_str, s._str);
				//为了防止空间开辟失败把原来的空间中的数据给破坏,下面的写法就更好
				char* tmp = new char[s._capacity + 1];//解决原理:先开空间,再销毁,再给给
				strcpy(tmp, s._str);
				delete[]_str;
				_str = tmp;//此时因为指针指向的空间,本质是内置类型,所以会自己去调用拷贝构造函数,不需要调用我们自己实现的拷贝构造函数
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}
		~String()
		{
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str()
		{
			return _str;
		}
		char& operator[](size_t pos)//普通类型使用,下面的是给特殊的const类型函数使用
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos)const//后面位置上给了const前面就一定也要给一个const,因为此时加了const导致返回值的类型也变成了const类型
		{
			assert(pos < _size);
			return _str[pos];
		}
		size_t size()const
		{
			return _size;
		}//运算符重载
		// 所以得出结论,只要是函数内部不进行数据修改的,我们就把const给加上
		bool operator>(const String& s)const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator==(const String& s)const
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator>=(const String& s)const
		{
			return *this > s || *this == s;
			//return *this > s || s == *this;//此时就是简单的把赋值顺序调换一下,该代码就是有问题的,因为此时的s是const类型的,妥妥的权利放大
		}                                  
		bool operator<(const String& s)const
		{
			return !(*this >= s);
		}
		bool operator<=(const String& s)const
		{
			return !(*this > s);
		}
		bool operator!=(const String& s)const
		{
			return !(*this == s);
		}
		void resize(size_t n, char ch = '\0')
		{
			int len = strlen(_str);
			if (_capacity < n)
			{
				reserve(2 * n);
			}
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
				return;
			}
			_size = n;
			for (int i = len; i < _size; i++)
			{
				_str[i] = ch;
			}
			_str[_size] = '\0';
		}
		void reserve(size_t n)//此时的这个n参数表示的就是n需要扩n个空间
		{
			char* tmp = new char[n + 1];//此时为了像上述一样,防止开辟失败,所以先开辟,再赋值(注意:capacity和字符个数的区别,capacity少1)
			strcpy(tmp, _str);//此时就是注意:只要是在类里面的函数都是自带一个this指针的类对象
			delete[]_str;
			_str = tmp;
			_capacity = n;//开空间跟_size是没有关系的,只有插入数据的时候才跟_size有关系
		}
		void push_back(char ch)//注意:有一个this指针,此时就是为了在这个this指针后面插入字符
		{
			if (_size == _capacity)//这种判断==的是需要多少扩多少(所以可以二倍二倍的扩)
			{
				//注意此时要配套使用,不可以使用realloc扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			_str[_size] = ch;//string的本质就是一个字符数组
			++_size;

			_str[_size] = '\0';//此时就是因为插入字符之后,把原来的\0给搞没了,所以要重新给一个\0,不然就会导致无法计算strlen之类的问题
		}
		void append(const char* str)//注意此时这个函数是用来插入字符串的,不是上述用来插入字符的
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)//此时这个位置表示的就是插入len个字符(插入len个字符,刚好等于capacity可以,但是不可以超过)
			{
				//此时这种直接插入多个,就不可以2倍2倍的扩,需要一次性扩大一点
				reserve(_size + len);
			}

			strcpy(_str + _size, str);//该拷贝,是因为可以直接把原来字符串中的\0给覆盖掉
			//strcat(_str, str);//但是最好不要使用strcat,追加,目的是因为防止原字符串过长,\0不好找,因为strcat只有找到了\0才会进行追加
			_size += len;
			//字符串是不需要处理\0的,因为strcpy会拷贝\0
		}
		String& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		String& operator+=(const char* str)
		{
			append(str);
			return *this;
		}//某个位置插入、某个位置删除
		void insert(size_t pos, char ch)//某个位置插入字符
		{
			assert(pos <= _size);//防止传参的时候越界

			if (_size + 1 > _capacity)//还是因为等于的时候是刚刚好满了,所以不怕,只有大于的时候才需要扩
			{
				reserve(2 * _capacity);
			}
			size_t end = _size;
			while (end >= pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}
			_str[pos] = ch;
			++_size;
		}
		void insert(size_t pos, const char* str)//某个位置插入字符串
		{
			assert(pos <= _size);

			size_t len = strlen(str);
			if (len == 0)
			{
				return;
			}
			if (_size + len > _capacity)
			{
				reserve(_capacity + len);
			}//扩容完之后就是插入数据
			size_t end = _size + len;
			for (int i = end; i >= pos + len; --i)
			{
				_str[i] = _str[i - len - 1];
			}
			char c = _str[pos + len];
			strcpy(_str + pos, str);
			_str[pos + _size] = c;
			_size += len;
			_str[_size] = '\0';
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos>=0 && pos <= _size);

			size_t end = _size;
			while (end > pos)
			{
				_str[end - 1] = _str[end];
				--end;
			}
			--_size;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static size_t npos;//npos此时给一个静态成员变量,供给大家使用
		//有一个特例,可以不需要在全局定义static,但是只针对于整形,就是加一个const
		//static const size_t npos = -1;
		//static const size_t N = 10;
		//估计是为了可以这样使用:int _arr[N];
	};

	size_t String::npos = -1;

	void Print(const String& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			cout << s[i] << " ";//此时因为这个函数就是一个const修饰的函数,所以无论在函数内存调用运算符重载,还是别的函数,此时这些函数都需要有const属性,所以导致我们需要实现两个[]运算符重载,一个给普通类型使用,一个给const类型使用
		}
		cout << endl;
		for (auto ch : s)//证明const属性的迭代器(所以需要把迭代器也给弄成两份,一份普通类型,一份const类型)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string1()
	{
		String s1;
		String s2("hello world");

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		for (int i = 0; i < 10; i++)
		{
			s2[i]++;
		}

		String s3(s2);//拷贝构造(经典的指针指向同一块空间问题),涉及深拷贝
		cout << s3.c_str() << endl;
		cout << s2.c_str() << endl;
		
		s3 = s2;//赋值要注意有自己给自己赋值的时候 s2 = s2;
		cout << s3.c_str() << endl;
		cout << s2.c_str() << endl;
	}
	void test_string2()//验证const修饰的函数需要使用具有const属性的函数
	{
		wwx::String s1("hello world");
		for (size_t i = 0; i < s1.size(); ++i)//注意:此时访问的不是string类中的成员变量,访问的是计算size大小的公有函数
		{
			cout << ++s1[i] << " ";
		}
		cout << endl;

		Print(s1);
	}
	void test_string3()//验证正向迭代器(反向迭代器先不学)
	{
		String s1("gdkkn vnqkc");
		String::iterator it = s1.begin();//普通it类型
		String::const_iterator it2 = s1.begin();//const类型的it
		while (it != s1.end())
		{
			cout << ++(*it) << " ";//指针不仅可以读,而且可以写
			++it;//虽然被const修饰,但是自己是可以修改的,例:++it是可以的++(*it)就是不可以的(总:自己可以修改,只是指向的内容不可以修改而已)
			++it2;
			//cout << ++(*it2) << " ";//const迭代器指向的内容不允许被修改
		}
		cout << endl;

		for (auto ch : s1)//很好的证明了,范围for就是使用迭代器实现的(傻白甜)
		{
			cout << ch << " ";
		}
		cout << endl;
	}
	void test_string4()//验证运算符重载
	{
		string s1("hello world");
		string s2("hello world");
		string s3("xxxxxxxxxxx");
		//比大小,此时比的是ASCII码值
		cout << (s1 < s3) << endl;//涉及运算符的优先级,所以要加上()
		cout << (s1 == s3) << endl;
		cout << (s1 == s2) << endl;
		cout << (s1 >= s3) << endl;
		cout << (s1 > s3) << endl;
		cout << (s1 != s2) << endl;
	}
	void test_string5()//验证字符和字符串追加
	{
		string s1("hello world");
		s1.push_back(' ');//反正就是注意使用this指针就行(因为this指针代表的就是s1对象)
		cout << s1.c_str() << endl;

		s1.append("xxxxxxxxxxxxxxxx");

		cout << s1.c_str() << endl;

		s1 += "aaaaaaaaaaaaaaaaaa";
		cout << s1.c_str() << endl;

	}
	void test_string6()
	{
		String s1("hello world");
		s1.insert(6, 'm');
		s1.insert(7, 'y');
		s1.insert(8, ' ');//搞定了中间插入,此时要防止是在最头上插入等问题
		cout << s1.c_str() << endl;
		s1.insert(5, "bit");
		cout << s1.c_str() << endl;
		s1.erase(5, 3);
		cout << s1.c_str() << endl;
	}
}

以上就是string类地基部分代码,注释很全,注意:测试代码需要放到test.cpp文件中测试

在这里插入图片描述

总结:还是那句话,自己实现string类,可以把以前学的知识得到很好的巩固。

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

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

相关文章

vue(5)

文章目录1. 监测数据原理1.1 通过问题引出1.2 开始1.3 Vue.set() 方法1.4 vue 监视 数组1.5 小练习2. 收集表数据3. 过滤器4. 内置指令4.1 v-text4.2 v-html4.3 v-cloak4.4 v-once4.5 v-pre1. 监测数据原理 1.1 通过问题引出 1.2 开始 要想解决上面的这个问题 &#xff0c;需要…

python基于django幼儿园管理系统

目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2.1 JAVA简介 4 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2.5 SPRINGBOOT框架 5 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3…

【2022.12.9】Lammps+Python 在计算g6(r)时遇到的问题

目录写在前面绘制g6( r )执行步骤【updated】如何检查图像的正确性&#xff1a;不是编程问题&#xff0c;而是数学问题的一个小bug废稿2则&#xff1a;写在前面 全部log&#xff1a; 【2022.11.16】LammpsPythonMATLAB在绘制维诺图时遇到的问题 绘制g6( r )执行步骤【updated…

Eureka原理浅析

文章目录1.简介2.组件2.1 Eureka Server2.1.1 主要功能2.1.2 自我保护机制2.1.3 数据同步方式2.1.4 Server的多级缓存和Client实例过期策略2.2 Eureka Client3.补充3.1 CAP偏重点3.2 功能扩展性3.3 工作流程1.简介 Eureka是Netflix开发的服务发现框架&#xff0c;本身是基于RE…

SegNeXt: 重新思考基于卷积注意力的语义分割

论文信息 论文名称&#xff1a;SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation 项目GitHub&#xff1a; GitHub - Visual-Attention-Network/SegNeXt: Official Pytorch implementations for "SegNeXt: Rethinking Convolutional Atten…

ESP-C3入门11. 创建最基本的HTTP请求

ESP-C3入门11. 创建最基本的HTTP请求一、menuconfig配置二、配置 CMakeLists1. 设置项目的额外组件目录2. 设置头文件搜索目录三、在 ESP32 上执行 HTTP 请求的基本步骤1. 创建 TCP 连接2. 设置 HTTP 请求3. 发送 HTTP 请求4. 接收 HTTP 响应5. 处理 HTTP 响应6. 关闭 TCP 连接…

35岁以上的大龄测试员们,后来都干什么去了?

为什么软件测试行业看不见白发苍苍的软件测试员?大龄测试员都去哪里了?各个公司会辞退大龄测试员吗? 如果一位 50 多岁的测试员申请 20 多岁或 30 多岁的职位&#xff0c;有多少公司会雇用他们呢?关于这个问题&#xff0c;有很多流言传说&#xff0c;也有一些残酷的现实。…

努力优化和改造不好的环境,去设计新的、积极的、适合自己的环境

你知道环境对你的影响有多大吗&#xff1f;自己的的社交圈也是一个环境如果你待在一个只知道吃喝玩乐&#xff0c;不思进取&#xff0c;天天玩手机、打游戏的圈子里那你很大程度也会被影响&#xff0c;因为你不跟他们一起你就融入不进去&#xff0c;就会被孤立&#xff0c;很多…

优秀蓝牙耳机推荐,热销不错的四款蓝牙耳机推荐

蓝牙耳机作为目前最流行的数码产品&#xff0c;受到很多人追捧&#xff0c;蓝牙耳机摆脱了有线蓝牙耳机的束缚&#xff0c;能够更好听歌打游戏&#xff0c;随时取用&#xff0c;更为便利&#xff0c;当然&#xff0c;随着耳机的大幅度创新&#xff0c;也导致很多人在选购耳机的…

内网渗透(四十三)之横向移动篇-SMB远程执行命令横向移动

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

前端性能优化的一些技巧(90% chatGpt生成)

终于弄好了chatGpt的账号&#xff0c;赶紧来体验一波。先来一波结论&#xff0c;这篇文章的主要内容来源&#xff0c;90%是用chatGpt生成的。先上chatGpt的生成的结果&#xff1a;作为一名懒惰的程序员&#xff0c;chatGpt会帮助我变得更懒...&#xff0c;好了下面开始文章的正…

GEE学习笔记 六十八:【GEE之Python版教程二】配置Python开发环境

这一篇内容主要讲解两部分内容&#xff0c;第一部分是本地python开发环境的配置&#xff0c;第二部分是GEE的python开发环境配置。我这里做的所有的操作都是在我的Mac电脑上做的&#xff0c;Windows上操作类似&#xff0c;如果有不清楚的可以自行搜索相关操作步骤。 第一部分&…

pytorch零基础实现语义分割项目(四)——模型训练与预测

模型训练与预测项目列表前言损失函数one_hotDice LossFocal Loss模型参数与训练预测项目列表 语义分割项目&#xff08;一&#xff09;——数据概况及预处理 语义分割项目&#xff08;二&#xff09;——标签转换与数据加载 语义分割项目&#xff08;三&#xff09;——语义…

winserver服务器硬盘满了怎么清理? 服务器硬盘空间不足清理方法

本文主要介绍我在维护windows server服务器期间总结的一些磁盘清理方式。如对您有所帮助&#xff0c;不甚荣幸。 文章目录一、C盘清理1. System32的日志文件2. IIS的日志文件3. .Net Framework的缓存文件4. 清理其他不必要文件5. 虚拟内存从c盘移到其他硬盘二、其他软件清理1. …

【离散数学】4. 图论

1.数理逻辑 2. 集合论 3. 代数系统 4. 图论 图&#xff1a;点边边与点的映射函数 连通性与判别 欧拉图与哈密尔顿图 二分图和平面图与欧拉公式 树及生成树 单源点最短路径&#xff1a;Dijkstra算法 对偶图 4. 图论 4.1 图的基本概念 4.1.1 图 一个图G是一个三重组 <V(G),E…

【LeetCode】No.232. 用栈实现队列 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/implement-queue-using-stacks/ 1. 题目介绍&#xff08;232. 用栈实现队列&#xff09; 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff…

两年外包生涯做完,感觉自己废了一半....

先说一下自己的情况。大专生&#xff0c;17年通过校招进入湖南某软件公司&#xff0c;干了接近2年的点点点&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了五年的功能测试…

慕了没?3年经验,3轮技术面+1轮HR面,拿下字节30k*16薪offer

前段时间有个朋友出去面试&#xff0c;这次他面试目标比较清晰&#xff0c;面的都是业务量大、业务比较核心的部门。前前后后去了不少公司&#xff0c;几家大厂里&#xff0c;他说给他印象最深的是字节3轮技术面1轮HR面&#xff0c;他最终拿到了30k*16薪的offer。第一轮主要考察…

MyBatis-Plus详细讲解(整合spring Boot)

哈喽&#xff0c;大家好&#xff0c;今天带大家了解的是MyBatis-Plus&#xff08;简称 MP&#xff09;&#xff0c;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。首先说一下MyBatis-Plus的愿景是什么&…

十五.程序环境和预处理

文章目录一.程序翻译环境和执行环境1.ANSI C 标准2.程序的翻译环境和执行环境二.程序编译和链接1.翻译环境2.编译本身的几个阶段3.运行环境三.预处理1.预定义符号2.#define&#xff08;1&#xff09;#define定义标识符&#xff08;2&#xff09;#define定义宏&#xff08;3&…