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

文章目录

    • 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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习基础】一元线性回归(适合初学者的保姆级文章)

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a; 【机器学习基础】机器学习入门&#xff08;1&#xff09; 【机器学习基…

CSS 面试题汇总

CSS 面试题汇总 1. 介绍下 BFC 及其应 参考答案&#xff1a; 参考答案&#xff1a; 所谓 BFC&#xff0c;指的是一个独立的布局环境&#xff0c;BFC 内部的元素布局与外部互不影响。 触发 BFC 的方式有很多&#xff0c;常见的有&#xff1a; 设置浮动overflow 设置为 auto、scr…

【LNMP】云导航项目部署及环境搭建(复杂)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、项目介绍1.1项目环境架构LNMP1.2项目代码说明 二、项目环境搭建2.1 Nginx安装2.2 php安装2.3 nginx配置和php配置2.3.1 修改nginx文件2.3.2 修改vim /etc/p…

精品基于SpringBoot的体育馆场地预约赛事管理系统的设计与实现-选座

《[含文档PPT源码等]精品基于SpringBoot的体育馆管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#…

【尚硅谷】MybatisPlus 学习笔记(下)

目录 六、插件 6.1、分页插件 6.1.1、添加配置类 6.1.2、测试 6.2、xml自定义分页 6.2.1、UserMapper中定义接口方法 6.2.2、UserMapper.xml中编写SQL 6.2.3、测试 6.3、乐观锁 6.3.1、场景 6.3.2、乐观锁与悲观锁 6.3.3、模拟修改冲突 数据库中增加商品表 添加数…

LeetCode 第一题: 两数之和

文章目录 第一题: 两数之和题目描述示例 解题思路Go语言实现 - 一遍哈希表法C实现算法分析 排序和双指针法Go语言实现 - 排序和双指针法C算法分析 暴力法Go语言实现 - 暴力法C算法分析 二分搜索法Go语言实现 - 二分搜索法C算法分析 第一题: 两数之和 ‍ 题目描述 给定一个整…

规则持久化(Sentinel)

规则持久化 基于Nacos配置中心实现推送 引入依赖 <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId> </dependency> 流控配置文件 [{"resource":"/order/flow",…

vue+nodejs+uniapp婚纱定制婚庆摄影系统 微信小程序 springboot+python

目前移动互联网大行其道&#xff0c;人人都手中拿着智能机&#xff0c;手机手机&#xff0c;手不离机&#xff0c;如果开发一个用在手机上的程序软件&#xff0c;那是多么的符合潮流&#xff0c;符合管理者和客户的理想。本次就是开发婚庆摄影小程序&#xff0c;有管理员&#…

k8s学习笔记-基础概念

&#xff08;作者&#xff1a;陈玓玏&#xff09; deployment特别的地方在于replica和selector&#xff0c;docker根据镜像起容器&#xff0c;pod控制容器&#xff0c;job、cronjob、deployment控制pod&#xff0c;job做离线任务&#xff0c;pod大多一次性的&#xff0c;cronj…

【蓝桥杯】拓扑排序

一.拓扑排序 1.定义&#xff1a; 设G&#xff08;V&#xff0c;E&#xff09;是一个具有n个顶点的有向图&#xff0c;V中的顶点序列称为一个拓扑序列&#xff0c;当且仅当满足下列条件&#xff1a;若从顶点到有一条路径&#xff0c;则在顶点序列中顶点必在之前。 2.基本思想…

Vue+SpringBoot打造音乐偏好度推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1.2 我的喜好模块2.1.3 每日推荐模块2.1.4 通知公告模块 2.2 用例图设计2.3 实体类设计2.4 数据库设计 三、系统展示3.1 登录注册3.2 音乐档案模块3.3 音乐每日推荐模块3.4 通知公告模…

【安装】CentOS 7 使用 OUI 图形界面安装 Oracle Database 19.3

需安装使用 X Server 协议的软件&#xff08;如 Xorg&#xff09;和如桌面图形软件&#xff08;Gnome 或 KDE&#xff09;。 使用 root 用户执行&#xff1a; # curl -o oracle-database-preinstall-19c-1.0-1.el7.x86_64.rpm https://yum.oracle.com/repo/OracleLinux/OL7/l…

istio系列教程

istio学习记录——安装https://suxueit.com/article_detail/otVbfI0BWZdDRfKqvP3Gistio学习记录——体验bookinfo及可视化观测https://suxueit.com/article_detail/o9VdfI0BWZdDRfKqlv0r istio学习记录——kiali介绍https://suxueit.com/article_detail/pNVbfY0BWZdDRfKqX_0K …

在Node.js中如何实现用户身份验证和授权

当涉及到构建安全的应用程序时&#xff0c;用户身份验证和授权是至关重要的一环。在Node.js中&#xff0c;我们可以利用一些流行的库和技术来实现这些功能&#xff0c;确保我们的应用程序具有所需的安全性。本篇博客将介绍如何在Node.js中实现用户身份验证和授权。 用户身份验…

“激发无限创意:在线图片制作,点亮你的视觉灵感!“

在数字时代&#xff0c;图片已经成为我们表达创意、分享故事和展示个性的重要方式。然而&#xff0c;你是否曾因为缺乏专业的设计技能或繁琐的制作流程而感到困扰&#xff1f;现在&#xff0c;有了在线图片制作平台&#xff0c;释放你的创意灵感变得前所未有的简单和方便&#…

福特锐界2021plus 汽车保养手册

福特锐界2021plus汽车保养手册两页&#xff0c;零部件保养要求&#xff0c;电子版放这里方便查询&#xff1a;

8-pytorch-损失函数与反向传播

b站小土堆pytorch教程学习笔记 根据loss更新模型参数 1.计算实际输出与目标之间的差距 2.为我们更新输出提供一定的依据&#xff08;反向传播&#xff09; 1 MSEloss import torch from torch.nn import L1Loss from torch import nninputstorch.tensor([1,2,3],dtypetorch.fl…

MySQL——基础内容

目录 第01章_数据库概述 关系型数据库(RDBMS)——表、关系模型 非关系型数据库(非RDBMS) 表、记录、字段 表的关联关系 一对一关联 一对多关系 多对多 自我引用 第02章_MySQL环境搭建 登录命令 常用命令 show databases; create database use 数据库名 show tables 第03章…

五种多目标优化算法(MOCS、MOFA、NSWOA、MOAHA、MOPSO)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 多目标优化算法是用于解决具有多个目标函数的优化问题的一类算法。其求解流程通常包括以下几个步骤&#xff1a; 1. 定义问题&#xff1a;首先需要明确问题的目标函数和约束条件。多目标优化问题通常涉及多个目标函数&#xff0c;这些目标函数可能…