c++之ini配置文件的详细解析

news2024/11/20 0:33:49

文章目录

    • ini文件概要
    • 代码实例分析
    • 小结

ini文件概要

        ini文件是一种系统配置文件,它有特定的格式组成。通常做法,我们读取ini文件并按照ini格式进行解析即可。在c++语言中,提供了模板类的功能,所以我们可以提供一个更通用的模板类来解析ini文件。

1、为什么要使用ini或者其它(例如xml,json)配置文件?


如果我们程序没有任何配置文件时,这样的程序对外是全封闭的,一旦程序需要修改一些参数必须要修改程序代码本身并重新编译,这样很不好,所以要用配置文件,让程序发布后还能根据需要进行必要的配置;配置文件有很多如INI配置文件,XML配置文件,还有就是可以使用系统注册表等。注意:ini的后缀名也不一定是".ini"也可以是".cfg",“.conf ”或者是”.txt"。因为ini文件实质就是txt文本文件。

配置文件除了读取外还可以写入:例如用户设置了习惯模式,这样下次启动读取文件的时候也是该模式

kv文件,是ini执行后出现的文件

2、ini配置文件的格式

  • 注释:以分号(;)开头,在我这个IniConfigParser实现中会视为注释行
  • Section:这是划分不同用途的配置选项分类管理的逻辑划分单元,一个Section下包含多个Option
  • Option:每个Section下就包含多个Option,一个Option就对应一个Key-Value-Pair.
  • Document:是表示整个ini配置文档的,他直接管理对象单元是不同名称的Section.

补充:

ini文件有特定格式,由节、键、值组成。

节表示一段数据开头,用[]包含。之后的内容为“键=值”组成。注释使用分号表示(;)。在分号后面的文字,直到该行结尾都全部为注解。

如下为一个ini文件的例子:

1)对节进行说明:

所有的parameters都是以sections为单位结合在一起的。所有的section名称都是独占一行,并且sections名字都被方括号包围着([ and ])。在section声明后的所有parameters都是属于该section。对于一个section没有明显的结束标志符,一个section的开始就是上一个section的结束,或者是end of the file。Sections一般情况下不能被nested,当然特殊情况下也可以实现sections的嵌套。因为INI文件可能是项目中共用的,所以使用[Section Name]段名来区分不同用途的参数区。


2)对参数进行说明:

INI所包含的最基本的“元素”就是parameter;每一个parameter都有一个name(key)和一个value,name和value是由等号“=”隔开。name在等号的左边。


3)对注释内容进行说明:

在INI文件中注释语句是以分号 “;” 开始的(有些人定义类是由#注释,例如下面的RrConfig)。所有的所有的注释语句不管多长都是独占一行直到结束的,在分号和行结束符之间的所有内容都是被忽略的。

2、ini配置文件实现方式

解析文件后按照状态流转进行读取, 如下:

全部读取文件内容到内存后, 按照状态流转一次循环全部字符完成解析,解析内容存放成2级map结构。

代码实例分析

解析ini配置文件源码

CParseIniFile.h

#pragma once


#include <fstream>
#include <iostream>
#include <string>
#include <map>
using namespace std;

#define COMMENT_CHAR '#'

class CParseIniFile
{
public:
	CParseIniFile();
	~CParseIniFile();
	bool ReadConfig(const string& filename, map<string, string>& mContent, const char* section);
	bool AnalyseLine(const string & line, string & key, string & val);
	void Trim(string & str);
	bool IsSpace(char c);
	bool IsCommentChar(char c);
	void PrintConfig(const map<string, string> & mContent);
private:
};


CParseIniFile.cpp


#include "CParseIniFile.h"



CParseIniFile::CParseIniFile()
{
}

CParseIniFile::~CParseIniFile()
{

}

bool CParseIniFile::ReadConfig(const string& filename, map<string, string>& mContent, const char* section)
{
	mContent.clear();
	ifstream infile(filename.c_str());
	if (!infile)
	{
		//LOG4CXX_ERROR(logger, "file open error!");
		return false;
	}
	string line, key, value;
	int pos = 0;
	string Tsection = string("[") + section + "]";
    // string Tsection = "[" + section + "]";//错误写法invalid operands of types ‘const char [2]’ and ‘const char*’ to binary ‘operator+’
    cout<< "Tsection: " << Tsection << endl;  //输出:Tsection: [Student1]

	bool flag = false;
	while (getline(infile, line))
	{
		if (!flag)
		{
			pos = line.find(Tsection, 0); // 查找节 Tsection 第一次出现的位置 string中的 find 返回的是int类型,找到就返回所在位置,未找到返回-1 ;0 代表从0位置开始查找
			if (-1 == pos)
			{
				continue;  //继续找
			}
			else //找到第一个出现的[节]
			{
				flag = true;
				getline(infile, line);//将流对象infile 中的数据逐行存取到line中
                cout<< "line: "<< line << " length: "<< line.length() << endl;   
                //输出:line: name = liubei length: 13 此处length包含空格和“=”
			}
		}
 //length()函数是string的内置成员方,用于返回string类型字符串的实际长度。
		if (0 < line.length() && '[' == line.at(0)) //line.at(0) 表示获取字符串line的第0号位字符
		{
			cout<< "line.length():" << line.length() << " line.at(0): " << line.at(0) << endl; //输出:line.length():10 line.at(0): [
            break;
		}
		if (0 < line.length() && AnalyseLine(line, key, value))
		{

			if (value.length() > 0)
			{
				if (value[value.size() - 1] == '\r')
				{
					value[value.size() - 1] = '\0';  //查看string赋值操作,可以直接把字符赋值给当前的字符串,此处表示对最后一位字符赋值0
				}
			}
			mContent[key] = value;
		}
	}
	infile.close();
	return true;
}

bool CParseIniFile::AnalyseLine(const string & line, string & key, string & val)
{
	if (line.empty())
	{
		return false;
	}
	int start_pos = 0, end_pos = line.size() - 1, pos = 0;
	if ((pos = line.find(COMMENT_CHAR)) != -1)
	{
		if (0 == pos)
		{//行的第一个字符就是注释字符
			return false;
		}
		end_pos = pos - 1;
	}
	string new_line = line.substr(start_pos, start_pos + 1 - end_pos);  // 预处理,删除注释部分

	if ((pos = new_line.find('=')) == -1)
	{
		return false;  // 没有=号
	}

	key = new_line.substr(0, pos);
	val = new_line.substr(pos + 1, end_pos + 1 - (pos + 1));

	Trim(key);
	if (key.empty())
	{
		return false;
	}
	Trim(val);
	return true;
}

void CParseIniFile::Trim(string & str)
{
	if (str.empty())
	{
		return;
	}
	int i, start_pos, end_pos;
	for (i = 0; i < str.size(); ++i)
	{
		if (!IsSpace(str[i]))
		{
			break;
		}
	}
	if (i == str.size())
	{ //全部是空白字符串
		str = "";
		return;
	}

	start_pos = i;

	for (i = str.size() - 1; i >= 0; --i)
	{
		if (!IsSpace(str[i]))
		{
			break;
		}
	}
	end_pos = i;

	str = str.substr(start_pos, end_pos - start_pos + 1);
}

bool CParseIniFile::IsSpace(char c)
{
	if (' ' == c || '\t' == c)
	{
		return true;
	}
	return false;
}

bool CParseIniFile::IsCommentChar(char c)
{
	switch (c)
	{
	case COMMENT_CHAR:
		return true;
	default:
		return false;
	}
}

void CParseIniFile::PrintConfig(const map<string, string> & mContent)
{
	map<string, string>::const_iterator mite = mContent.begin();
	for (; mite != mContent.end(); ++mite)
	{
		cout << mite->first << "=" << mite->second << endl;
	}
}

config.ini文件

[Student1]
name = liubei
age = 28
record = 8
[Student2]
;name = zhangfei  //注释
age = 29
record = 4

[Student3]
name = guanyu
age = 26
record = 6

主函数

#include "CParseIniFile.h"
#include <iostream>
#include <stdio.h>
#define _KEY_ "name" //用宏定义name为_KEY_
int main() {
  // string fileName;
  // string section = "Student1";
  map<string, string> fname;
  CParseIniFile config;

  bool flage = config.ReadConfig("config.ini", fname, "Student1"); //参数一:配置文件名(或路经名);参数二:map容器 参数三:需要写入map容器中的节

  if (flage) {  //判断节内的内容是否成功读取到map容器
    auto name = fname["name"];
    auto age =
        fname["age"]; // age是key,此处用到map容器的一种赋值方式map[key] = value
                      // 此处key是字符串,必须加“”,要不然找不到变量定义
    auto record = stod(fname["record"]); // stod 将字符串转化成double型

    cout << name << endl;
    cout << age << endl;
    cout << record << endl;
  }

  bool flage2 = config.ReadConfig("config.ini", fname, "Student2");
  if (flage2) {
   //name的另一种获取方法
    auto Iter = fname.find(_KEY_); //利用map容器的find()查找节Student2下的name
    //判断该节中是否有name,或者查到name,但其对应的value为空,我们会给其赋一个默认值
    string name;
    if ((fname.cend() == Iter) || (!(Iter->second.size()))) {
      name = "zhaoyun";
    } else {
      name = Iter->second;
    }

    auto age = atof(fname["age"].c_str());
    auto record = stod(fname["record"]);

    cout << name << endl;
    cout << age << endl;
    cout << record << endl;
  }

  // config.PrintConfig(fname);
}

注意:

1 map容器的查找和key和value的赋值请参考:

https://blog.csdn.net/zxy_ZXY123/article/details/135966846

map 容器的详细总结-CSDN博客

2 c_str()及atof()和stod()的用法参考:

c++中c_str()及atof()和stod()的用法详细解析-CSDN博客

3 string 字符串长度的获取以及find()查找及子串获取 substr()参考:

C++ string的详细总结-CSDN博客


基于知识补充: 

‘\n’ 换行,光标移到下一行的开头;

'\r' 回车,光标移到当前行的开头,不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖;

\t:表示水平制表空行操作,相当于Tab键,不会换行

{
    cout << "this is the first line\n";  

    cout << "this is the second line\r";  

    cout << "this is the third line\n";  //会覆盖第二行length长度的部分

    cout << "this is the fouth line\r";  

    cout << "this is the fifth line\n"; 

    cout<<"First"<<"\n"<<"Second"<<endl; 

    cout<<"First123"<<"\r"<<"Second"<<endl;  //不换行,光标回到本行首位开始等长度覆盖

    cout<<"First123"<<"\t"<<"Second"<<endl;  

    cout<<"First123"<<"\0"<<"Second"<<endl;  

}


输出:
this is the first line
this is the third linee
this is the fifth line
First
Second
Second23
First123	Second
First123Second

关于C++字符串中的"\0"问题

C++中没有字符串对象,字符串可以看成是字符数组,不过它们之间又有区别。

        简单的来说就是区别在最后的一个元素"\0"上,它标志着一串字符是否是字符串。用字符串初始化字符数组时,"\0"附带在后面与前面的字符一起作为字符数组的元素。

        在内存中,就是根据"\0"来确认字符串,如果找不到就会沿着字符一直找下去。它占用内存空间,但是不计入串长。

       用字符串初始化字符数组时,系统会在字符数组的末尾自动加上一个字符"\0",因此数组的大小比字符串中实际字符的个数大。如:sizeof(str1)=strlen(str1) +1;

       如果是用字符初始化数组,则一定要把"\0"作为一个元素放在初始值表中,不然就不会成为一个字符串。

①    ‘0’    代表    字符0  ,对应ASCII码值为   0x30 (也就是十进制 48)

②    '\0'    代表     空字符(转义字符)【输出为空】, 对应ASCII码值为   0x00(也就是十进制 0), 用作字符串结束符

③     0    代表     数字0,  若把 数字0 赋值给 某个字符,对应ASCII码值为    0x00(也就是十进制0) 

④     “0”  代表    一个字符串,  字符串中含有 2个字符,分别是 '0' 和  '\0'   

下面补充说明(帮助理解)

①   char  ch_0 = ‘0’;                   // 字符0 赋值给一个字符,实际赋的 码值 为 0x30,十进制48

      std::cout << ch_0 << '\n';      // 输出的 是 码值0x30 对应的 字符 0, 界面上看到的是0

      std::cout << int(ch_0) << '\n';    // 输出的 是字符 ‘0’ 对应的码值  0x30,即十进制48  ,界面上看到的是 48

②   char  ch_0 =  '\0';                 // 字符‘\0' 赋值给一个字符,实际赋的 码值 为 0x00,十进制0

      std::cout << ch_0 << '\n';     // 输出的 是 码值0x00 对应的 空字符【NULL】, 界面上看到的是 空白,什么也看不见

      std::cout << int(ch_0) << '\n';    // 输出的 是字符 ‘\0’ 对应的码值  0x00,即十进制0  ,界面上看到的是 0

③   char  ch_0 = 0;                    // 数字0 赋值给一个字符,实际赋的是 码值

       std::cout << ch_0 << '\n';    // 输出的 是 码值0 对应的 字符,此处为 空白字符,即输出为空,界面上什么也看不见

      std::cout << int(ch_0) << '\n';    // 输出的 是码值  0x00,即十进制0  ,界面上看到的是0

④   char ch_0[ ] = "0";               // 字符串 “0” 初始化字符数组

      std::cout << sizeof(ch_0) << ‘\n’;     // 输出 ch_0 字节数, 界面显示 为2

      std::cout << ch_0[0] << '\n';             // 输出字符 ‘0’,界面上看到的是 0 

      std::cout << ch_0[1] << '\n';            // 输出字符 ‘\0’,界面上看到的是 空白

      std::cout << int( ch_0[0] )<< '\n';     // 输出字符 ‘0’ 对应的码值 0x30,界面上看到的是 48

      std::cout << int ( ch_0[1] )<< '\n';    // 输出字符 ‘\0’ 对应的码值 0x00,界面上看到的是 0

 

注:以上代码 若是想拷贝试试,可能需要做相应的调整。因为为了方便大家学习,我有些地方多加了空格,能看得更清楚些,有问题欢迎留言探讨。

总结:记住几点

① 用 数值 给 某个 字符变量 赋值时,相当赋 与该数字相同码值 所对应的字符

② 用 字符 给 某个 字符变量赋值时, 即 赋 字符本身

③ ‘\0’ 对应的 码值为0, 界面显示为 空白

\0是C++中字符串的结尾标志,存储在字符串的结尾。比如char cha[5]表示可以放4个字符的字符串,由于c/c++中规定字符串的结尾标志为'\0',它虽然不计入串长,但要占内存空间,而一个汉字一般用两个字节表示,且c/c++中如一个数组cha[5],有5个变量,分别是 cha[0] , cha[1] , cha[2] , cha[3] , cha[4] , 所以cha[5]可以放4个字母(数组的长度必须比字符串的元素个数多1,用以存放字符串结束标志'\0')或者放2个汉字(1个汉字占2个字节,1个字母占一个字节),cha[5]占5个字节的内存空间。

'\0'的ASCII是0
例如:
char sText[5];
sText[0]='a';
sText[1]='a';
sText[2]='a';
sText[3]='a';
sText[4]='\0';
cout<<sText<<endl; //这样输出就是4个a
// 如果数组的第五个元素即:
sText[4]='a';
cout<<sText<<endl; //这样输出就是5个a和一堆乱码,甚至发生系统错误,因为该字符串没有字符串结尾符
  {
    // string s("liubei");
    //string s("zhangfeel\r");
    string s("zhangfeel");
    cout << "s.size(): " << s.size() << endl;  // \r占一个字符长度
    cout << "sizeof(s): " << sizeof(s) << endl;

    cout << "s.size() - 1: " << s.size() - 1 << endl;//实际上取的最后一位l
    cout<< "s1 = " << s << endl;
    cout << "s[s.size() - 1]1: " << s[s.size() - 1] << endl;

    s[s.size() - 1] = '\0';//对字符串最后一位置空
     cout<< "s2 = " << s << endl;
    cout << "s[s.size() - 1]2: " << s[s.size() - 1] << endl;

    if (s[s.size() - 1] == '\r') {
      s[s.size() - 1] = '\0';
      cout << "s[s.size() - 1]_if: " << s[s.size() - 1] << endl;
    }



输出:
s.size(): 9
sizeof(s): 32
s.size() - 1: 8
s1 = zhangfeel
s[s.size() - 1]1: l
s2 = zhangfee
s[s.size() - 1]2: 

string类对象以'\0'作为结束标志吗?

      从图中我们可以看见,'\0'被插入到了字符串中并计入到了size中,但是string类型在直接输出时,并没有以'\0'作为输出的结束标志;而转为C类型字符串输出时,把'\0'当作了结束标志。

       因此可以得出结论,string类型对象并不会以'\0'作为结束标志。

注意:
        ①C++中的string类对象会在末尾补上'\0',这是因为C++有C语言的历史包袱。因为C语言的字符串以'\0'结尾,所以为了方便在必要时将string字符串转为C类型字符串,所以string类型对象会在末尾补上一个不计入size和capacity的'\0'。

        ②C++中的string类对象并不会将'\0'作为结束标志,因为string类对象内部维护了一个记录自身长度的成员变量size,在输出string类对象时会根据size的大小决定输出多少个字符,而不是看'\0'的位置决定输出到哪个字符结束。

        注:用双引号括起来的字符串也属于C类型的字符串,所以它也是会以'\0'结尾的。

参考:【C++】string类构建的字符串以‘\0‘结束吗?_string输出是遇到\0停止还是不读取\0-CSDN博客

小结

声明:本文为学习总结,转载请注明出处!

参考:

1 C++实现解释ini配置文件的原理 - 知乎

2 https://jingyan.baidu.com/article/67508eb4c2fcf1ddcb1ce439.html

3 C++读取ini配置文件-CSDN博客

4 读写ini配置文件(C++)-CSDN博客

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

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

相关文章

【线程池项目(二)】线程池FIXED模式的实现

在上一篇【线程池项目&#xff08;一&#xff09;】项目介绍和代码展示 中&#xff0c;我们展示了线程池的两个版本实现&#xff0c;它们的代码在具体的实现细节上是优化过了的。下文提供的代码并非完整&#xff0c;也有很多地方尚需改善&#xff0c;但这些差异对理解整个项目而…

IT廉连看——C语言——分支语句

IT廉连看—分支语句 一、什么是语句 C语句可分为以下五类&#xff1a; 表达式语句 函数调用语句 控制语句 复合语句 空语句 本周后面介绍的是控制语句。 控制语句用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&…

字符串(算法竞赛)--字典树Trie与最大异或对

1、B站视频链接&#xff1a;F06 字典树(Trie)_哔哩哔哩_bilibili 题目链接&#xff1a;【模板】字典树 - 洛谷 #include <bits/stdc.h> using namespace std; const int N100010; int n; char s[N]; int ch[N][26];//ch[0][2]1表示0号节点通过c边走到了节点1 int cnt[…

2024最新前端面试题

数组是属于Object类型的&#xff0c;也就是引用类型&#xff0c;所以不能使用 typeof 来判断其具体类型。下面这些方法是判断数组的几种方法&#xff1a; 1、instanceof运算符 主要是判断某个实例&#xff08;arr&#xff09;是否属于某个对象。 let arr [1,2,3]; console.l…

eclipse中open Type 、 open type in Hierachy、open Resource的区别

目录 场景&#xff1a; open Type open Resource open type in Hierachy 场景&#xff1a; 在项目中想要研究底层代码&#xff0c;经常要用eclipse看依赖jar包的类&#xff0c;比如spring的源码中AbstractApplicationContext类CTLSHIFTT用的少&#xff0c;经常用的CTLSHIR…

给大家分享一款小程序:AI一秒修图

AI一秒修图 照片修复的AI助手特点&#xff1a;Demo&#xff08;1.选择图片 2.涂抹遮罩 3.消除&#xff09;Product Roadmap (版本演进)Contact-联系我们Reference 照片修复的AI助手 照片修复小小助手是一款快速P图微信小程序&#xff0c;用来消除图片中指定的人和物&#xff…

[算法沉淀记录] 排序算法 —— 冒泡排序

排序算法 —— 冒泡排序 基本概念 冒泡排序是一种简单的排序算法。它重复地遍历要排序的列表&#xff0c;一次比较两个元素&#xff0c;并交换它们的位置&#xff0c;如果它们不是按照升序排列的。这步遍历是重复进行的&#xff0c;直到没有再需要交换&#xff0c;也就是说该…

【设计模式】策略模式及函数式编程的替代

本文介绍策略模式以及使用函数式编程替代简单的策略模式。 策略模式 在策略模式&#xff08;Strategy Pattern&#xff09;中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 在策略模式定义了一系列算法或策略&#xff0c;并将每个算法封装在独立…

介绍 PIL+IPython.display+mtcnn for 音视频读取、标注

1. nn.NLLLoss是如何计算误差的? nn.NLLLoss是负对数似然损失函数&#xff0c;用于多分类问题中。它的计算方式如下&#xff1a;首先&#xff0c;对于每个样本&#xff0c;我们需要将其预测结果通过softmax函数转换为概率分布。softmax函数可以将一个向量映射为一个概率分布&…

Three.js加载PLY文件

这是官方的例子 three.js webgl - PLY 我在Vue3中使用&#xff0c;测试了好久始终不显示点云数据。在网上查询后发现ply文件要放置在public目录下才行 <el-row><el-button type"primary" class"el-btn" click"IniThree1">PLY</…

【C++初阶】--类和对象(下)

目录 一.const成员 1.权限放大问题 2.权限的缩小 二.再谈构造函数 1.构造函数体赋值 2.初始化列表 (1)概念 (2)使用 ①在对象实例化过程中&#xff0c;成员变量先依次进行初始化 ②再进行函数体内二次赋值 3.explicit关键字 (1)C为什么要存在自动隐式类型转换…

Java之线程同步、synchronized用法及原理

线程的同步 场景1&#xff1a;两个线程同时访问一个变量&#xff0c;一个线程自增&#xff0c;一个线程自减 public class thread11 {public static void main(String[] args) throws InterruptedException {Thread thread1 new AddThread();Thread thread2 new DecThread(…

编曲学习:高叠和弦 挂留和弦 和弦实战应用

高叠和弦 挂留和弦 和弦实战应用小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65d4826fe4b04c10a1310517?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 七和弦 以三和弦举例,三和弦上面叠一个三度的音,就变成了七和弦。 从下到…

opencv python投影变换效果

变换原理&#xff1a; https://www.cnblogs.com/txwtech/p/18024547 python示范代码&#xff1a; src2原图&#xff0c;4个坐标点 dst2转换后&#xff0c;4个坐标点 p_touyin cv2.getPerspectiveTransform(src2,dst2) #计算投影变换矩阵 #利用矩阵值进行图像投影变换 r…

全流程点云机器学习(二)使用PaddlePaddle进行PointNet的机器学习训练和评估

前言 这不是高支模项目需要嘛&#xff0c;他们用传统算法切那个横杆竖杆流程复杂耗时很长&#xff0c;所以想能不能用机器学习完成这些工作&#xff0c;所以我就来整这个工作了。 基于上文的数据集切分 &#xff0c;现在来对切分好的数据来进行正式的训练。 本系列文章所用的…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture11 Advanced_CNN 实现GoogleNet和ResNet

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture11 Advanced_CNN 代码&#xff1a; Pytorch实现GoogleNet import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader import torch.nn as nn import torch.nn.fun…

内核解读之内存管理(8)什么是page cache

文章目录 0. 文件系统的层次结构1.什么是page cache2.感观认识page cache3. Page Cache的优缺点3.1 Page Cache 的优势3.2 Page Cache 的劣势 0. 文件系统的层次结构 在了解page cache之前&#xff0c;我们先看下文件系统的层次结构。 1 VFS 层 VFS &#xff08; Virtual Fi…

【Ubuntu】解决Ubuntu 22.04开机显示器颜色(高对比度/反色)异常的问题

使用Ubuntu 22.04时强制关机了一下&#xff08;make -j16把电脑搞崩了&#xff09;&#xff0c;开机后系统显示的颜色异常&#xff0c;类似高对比度或反色&#xff0c;如下图。看着很难受&#xff0c;字体也没办法辨认。还好之前遇到过类似的问题&#xff0c;应该是一个配置文件…

装修避坑干货|阳台洗衣柜洗衣机一体柜设计。福州中宅装饰,福州装修

装修的时候常常会在洗衣柜中嵌入洗衣机&#xff0c;其实阳台柜的安装并不像看起来的那么简单&#xff0c;下面给大家说说几个注意事项‼️ 01.水电位置 在安装阳台柜之前&#xff0c;务必确认水电管道的位置。确保阳台柜不会阻碍水电管道的使用&#xff0c;以免造成不必要的麻…

Three.js-02Vue框架入手

1.创建项目 说明&#xff1a;默认有vue基础&#xff0c;node版本18以上。 vue create threejs 2.选择vue3 4.安装 npm i three 5. 修改页面 <template> <div></div> </template><script setup> import * as THREE from three;const width win…