c++ 精学笔记记录Ⅱ

前言:

本章节主要开始记录关于常见的数据类型以及常见该数据类型的使用用法进行分析,记录,为之后的c++学习打下坚实的基础。

1.bool类型:

bool类型数据在使用时,需要考虑到内存空间的使用情况,目前的bool官方限定是1个字节,但也可以给bool类型数据赋多个字节。bool值非零就是true,否则为flase。

转化为bool表达式:都遵循bool值非零就是true,否则为flase。

1.逻辑运算符

2.位运算符

3.比较运算符

1.1逻辑运算符

操作数规则:无论操作数是数值还是表达式,都会转为布尔值

运算规则:基于"整体真假"判断,返回值是bool类型

核心特性:&&||有短路求值(按需计算,提升效率 / 避免错误)

运算符 名称 规则 短路特性 示例(a=5,b=0)
&& 逻辑与 全为真才返回 true,否则 false 左假则右不执行 a&&b → true&&false → false
|| 逻辑或 有一个真就返回 true,否则 false 左真则右不执行 `a b` → true|false → true
! 逻辑非 真→假,假→真(单目运算符) !a → !true → false

1.逻辑与(&& == and)

操作数 A 操作数 B A && B 说明
false false false 两边都为真,结果才为真
false true false 短路:A 为假,直接返回假
true false false 短路:B 为假,直接返回假
true true true 两边都为真,返回真

2.逻辑或(|| == or)

操作数 A 操作数 B A || B 说明
false false false 两边都为假,结果才为假
false true true 短路:B 为真,直接返回真
true false true 短路:A 为真,直接返回真
true true true 两边都为真,返回真

3.逻辑非(! == not)

操作数 A !A 说明
false true 取反,假变真
true false 取反,真变假

1.2 位运算符

  • 操作数规则:仅支持整数类型 (char/int/long 等,本质是二进制序列),不支持浮点数;
  • 运算规则:对操作数的每一位二进制位 逐一计算,返回值是整数类型(运算后的数值);
  • 核心特性:无短路,所有操作数都会被计算。
运算符 名称 规则(逐位计算) 短路特性 示例(a=6=0000 0110,b=3=0000 0011)
& 按位与 对应位都为 1 则 1,否则 0 a&b → 0000 0010 → 数值 2
| 按位或 对应位有 1 则 1,否则 0 `a b` → 0000 0111 → 数值 7
~ 按位取反 每一位取反(0→1,1→0,单目) ~a → 1111 1001(32 位 int)→ 数值 - 7
复制代码
#include <iostream>
#include<bitset>
using namespace std;

int main() {
	//算数运算符 
	//逐位非 ~  ~0->1,~1->0  ~101 -> 010
	//逐位与 &	1&1 = 1  0&1 = 0  0&0 = 0
	//逐位或 |	1|1 = 1  0|1 = 1   0|0 = 0

	char a = 0b10000001; //0b c++14
	char b = 0b00000001;

	//用bitset 输出二进制
	cout << "a:\t" << bitset<8>(a) << endl;
	cout << "b:\t" << bitset<8>(b) << endl;
	//逐位非操作
	cout << "~a:\t" << bitset<8>(~a) << endl;
	cout << "~b:\t" << bitset<8>(~b) << endl;
	//逐位与操作
	cout << "a&b:\t" << bitset<8>(a & b) << endl;
	//逐位或操作
	cout << "a|b:\t" << bitset<8>(a | b) << endl;


}

/*
a:      10000001
b:      00000001
~a:     01111110
~b:     11111110
a&b:    00000001
a|b:    10000001
*/

1.3逻辑运算符和位运算符的区别

运算符类型 返回值类型 示例(a=5,b=0)
逻辑运算符 强制 bool 类型 a&&b → false(bool)
位运算符 整数类型(与操作数一致) a&b → 0(int),`a b` →5(int)

实际开发过程中也会合理的运用短路来进行条件判断中进行操作,能有效的提升运算速度


2.string类型

本质:c++中,string类型数据在存储字符串的本质存储,符合下述存储逻辑。

2.1字符串的存储

1.字符串长度 ≤ SSO 缓冲区大小(GCC 为 15),字符直接存储在std::string对象的栈空间。

复制代码
┌──────────────────────────────── 栈空间(函数栈帧) ────────────────────────────────┐
│ ┌───────────────────────── std::string 对象 str1 ──────────────────────────┐       │
│ │ _M_size: 5                // 有效字符数("hello"共5个)                  │       │
│ │ _M_capacity: 15           // SSO缓冲区总容量,无堆分配                   │       │
│ │ _M_buf[16]:               // 16字节栈数组(15字符+1个\0,兼容C风格)    │       │
│ │   [0]='h', [1]='e', [2]='l', [3]='l', [4]='o', [5]='\0'                  │       │
│ │   [6]~[15]: 未使用(SSO缓冲区剩余空间)                                  │       │
│ └─────────────────────────────────────────────────────────────────────────┘       │
└───────────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────── 堆空间 ──────────────────────────────┐
│ (无任何内存分配)                                                │
└───────────────────────────────────────────────────────────────────┘

2.长字符串(超过SSO阈值,长度>15)

例:std::string str1 = "a long string more than 15 chars"; (长度 20)

复制代码
┌──────────────────────────────── 栈空间(函数栈帧) ────────────────────────────────┐
│ ┌───────────────────────── std::string 对象 str1 ──────────────────────────┐       │
│ │ _M_size: 20               // 有效字符数(20个)                        │       │
│ │ _M_capacity: 31           // 堆分配容量(通常按2倍扩容,31=2^5-1)      │       │
│ │ _M_ptr: 0x7fxxxx1234      // 指向堆内存的指针                          │       │
│ └─────────────────────────────────────────────────────────────────────────┘       │
└───────────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────── 堆空间(地址:0x7fxxxx1234) ──────────────────────────────┐
│ 'a',' ','l','o','n','g',' ','s','t','r','i','n','g',' ','m','o','r','e',' ','t'  // 20个有效字符 │
│ '\0'                                                                               // C++11后自动添加,兼容c_str() │
│ (剩余11字节:未使用,直到容量31)                                                   │
└──────────────────────────────────────────────────────────────────────────────────────────

2.2字符串常用函数

包括:size,capacity,substr,empty,to_string,stoi,+

常用string基础定义的案例

复制代码
#include <iostream>
#include <string>

using namespace std;

int main(){
	string str1{ "test string 001" };
	cout << "str1 = " << str1 << endl;
	str1 = "test string 002";
	cout << "str1 = " << str1 << endl;
	string str2{ str1 };
	cout << "str2 = " << str2 << endl;
	string str3;
	str3 = str2;
	cout << "str3 = " << str3 << endl;

	return 0;
}

size / length:获取该字符串的长度。

复制代码
#include <iostream>
#include <string>

using namespace std;	

int main(){

    string str4{ "1234567890" };
	//str4 = 10 字符串长度为10
	cout << "str4.size = " << str4.size() << endl; //str4.size = 10
    cout << "str4.length = " << str4.length() << endl; //str4.length = 10
}

capacity:获取实际分配空间的大小。

复制代码
#include <iostream>
#include <string>

using namespace std;	

int main(){

    string str4{ "1234567890" };
	//实际分配的空间大小是15 str4.capacity = 15
	cout << "str4.capacity = " << str4.capacity() << endl;
}

empty:空串判断

复制代码
#include <iostream>
#include <string>

using namespace std;	

int main(){
	//字符串的比较
	string strif;
	//空串判断
	if (strif.empty()) {
		cout << "strif empty empty()" << endl;
	}
	if (strif.length() == 0) {
		cout << "strif empty length()" << endl;
	}
	if (strif.size() == 0) {
		cout << "strif empty size()" << endl;
	}
	if (strif=="") {
		cout << "strif empty "" " << endl;
	}

    /*
    strif empty empty()
    strif empty length()
    strif empty size()
    strif empty
    */

}

stoi/stod/stoll:字符串转整形数字/浮点数/长整型

复制代码
#include <iostream>
#include <string>

using namespace std;

int main(){
    //字符串和数字之间的转换
	auto i1 = stoi("1234");
	++i1;
	cout << "i1 = " <<  i1 << endl;

	string d1 = "123.5";
	double d2 = stod(d1);
	cout << "d1 = " << d1 << endl;
	auto f1 = stof("33.1f");
	cout << "f1 = " << f1 << endl;
	auto f2 = stof("33.1");
	cout << "f2 = " << f2 << endl;

	cout << stoll("1213214432") << endl;

}

to_string:数字转字符串

复制代码
#include <iostream>
#include <string>

using namespace std;

int main(){
	//数字转字符串
	auto pistr = to_string(3.1415926);
	cout << "pistr = " << pistr << endl;
	cout << to_string(-199888)<< endl;

}

string + to_string(int/double):

字符串的拼接,注意整形和浮点类型在字符串中拼接要先转为string类型

复制代码
#include <iostream>
#include <string>

using namespace std;

int main(){
    //字符串的拼接
	string log;
	string txt = "login success!";
	string user = "wjj";
	int thread_id = 10;

	log = "[debug] " + user + ":" + txt + ":" + to_string(thread_id);
	cout << "log :" << log << endl;
}

2.3字符串的截取/索引/替换

包括:substr()/strfind.find()/strfind.replace()

substr():截断字符串(开始索引,获取长度)

复制代码
#include <iostream>
#include <string>

using namespace std;	

int main(){
    string str4{ "1234567890" };
	//截断字符串substr
	cout << str4.substr(3) << endl;	//从下标3开始获取数据(注意是0下标开始的)
	//从下标1开始取3个值,获取数据 234,注意不是从1到3,而是从1开始取3个数
	cout << str4.substr(1, 3) << endl;	
}

strfind.find():查找字符串(string),返回值为索引的开始位置

需要通过如下string::nXX进行判断有无查询到,查到了返回索引位置,没查到无返回值

复制代码
	auto pos = strfind.find("[test]");
	if (pos == string::npos) {
		//表示没有查找到这个值
		cout << "[test] not find" << endl;
	}
复制代码
#include <iostream>
#include <string>

using namespace std;	

int main(){

	//字符串的查找
	string strfind = "test for find [user] test";
	string suser = "[wjj]";
	auto pos = strfind.find("[test]");
	if (pos == string::npos) {
		//表示没有查找到这个值
		cout << "[test] not find" << endl;
	}
	string key = "[user]";
	pos = strfind.find(key);
	cout << "pos = " << pos << endl; 
	cout << "pos = " << strfind.substr(pos) << endl; 
	//pos =14 该值代表索引位置
}

strfind.replace():(替换开始的位置,替换多少size大小,代替的内容)

注意:替换会改变原字符串,需提前留备份,常与查找组合使用

复制代码
#include <iostream>
#include <string>

using namespace std;	

int main(){
	string strfind = "test for find [user] test";
	string suser = "[wjj]";
	auto pos = strfind.find("[test]");
	if (pos == string::npos) {
		//表示没有查找到这个值
		cout << "[test] not find" << endl;
	}
	string key = "[user]";
	pos = strfind.find(key);
	cout << "pos = " << pos << endl; 
	cout << "pos = " << strfind.substr(pos) << endl; 
	//pos =14 该值代表索引位置

	//查找到相应位置进行替换(从哪个位置开始,替换多少,不能做到把一个字符串替换到另一个字符串)
	//strfind.replace(哪个位置开始替换,替换的长度,替换的内容)
	if (pos != string::npos) {
		cout << strfind.substr(pos) << endl;
		auto bak = strfind; //replace 后原来的string会改变,所以需要备份
		auto result1 = strfind.replace(pos, //替换字符串的起始索引
			key.size(),//替换字符串的长度
			suser);//替换的内容
		//原内容
		cout << bak << endl;
		//替换后的内容
		cout << result1 << endl;
	}


}

3.枚举类型

简要说明:我们这里使用的是c++11之后的枚举方式,相比于c++11之前后者的好处

1.避免了不同枚举类型定义中相同枚举值产生冲突的情况。

2.枚举对象不能直接转为整数型参与对比获取,增强了其使用的安全性和防混性。

枚举的定义:c++11之后定义多了个class

复制代码
//c++11前枚举定义
enum MyEnum
{
	mjz1, //默认0
	mjz2,
	mjz3 = 100, //如果进行赋值,那其之后的值也会更改
	mjz4,
	mjz5,
	DEBUG
};
enum Status
{
	PLAY,
	PAUSE,
	STOP
};

//c++11后的枚举
enum class LogLevel
{
	DEBUG,
	INFO,
	ERROR,
	FATAL
};

调用的方式从c++11之前的直接获取内容对比,转变为另一种形式

复制代码
//c++11之前
Status status{ STOP }; //初始化获取值

//直接对比数字
if (status == 2) {
	cout << "2 STOP" << endl;
}
//直接改值
status = PAUSE;

//判断
if (status == PAUSE) {
	cout << "PAUSE" << endl;
}


//c++11之后

//初始化方式
LogLevel level{ LogLevel::INFO };

//对比数字需要强制转换
int s = static_cast<int>(level);

//修改值的方式
LogLevel log_level = LogLevel::ERROR;

//判断
if (level <= log_level) {
	cout << "记录日志" << endl;
}

完整代码如下:

复制代码
#include <iostream>
#include <string>
using namespace std;

//c++11前枚举定义
enum MyEnum
{
	mjz1, //默认0
	mjz2,
	mjz3 = 100, //如果进行赋值,那其之后的值也会更改
	mjz4,
	mjz5,
	DEBUG
};
enum Status
{
	PLAY,
	PAUSE,
	STOP
};

//c++11后的枚举
enum class LogLevel
{
	DEBUG,
	INFO,
	ERROR,
	FATAL
};

int main() {
	//使用方法类似于宏定义
	//枚举类型支持switch,效率会更高
	//消息类型,日志级别

	cout << "枚举值1 = " << mjz1 << endl;
	cout << "枚举值4 = " << mjz4 << endl;
	MyEnum mgz{ mjz5 };
	Status status{ STOP };

	if (status == 2) {
		cout << "2 STOP" << endl;
	}
	status = PAUSE;
	if (status == PAUSE) {
		cout << "PAUSE" << endl;
	}

	//设置日志显示的级别,常用枚举比较大小来确认等级
	//c++11之前是可以之间跟整数做访问对比,c++11之后是不能这样做的,避免出现混乱不清晰。

	//c++11之后不能之间访问内部内容,需要用如下方式调用
	//如果多个枚举之间就是会相同c++11之前会报错,c++11之后的访问方式不会报错
	LogLevel level{ LogLevel::INFO };
	//c++11之后的转换就得这样强制转换
	int s = static_cast<int>(level);
	LogLevel log_level = LogLevel::ERROR;
	if (level <= log_level) {
		cout << "记录日志" << endl;
	}

}

4.main函数的参数传递

4.1参数说明

main函数中有三种参数传递:

复制代码
//argc:从程序运行的环境中传递给程序的实参个数
//argv:字符串数组
//env环境变量,一般不用

int main()
//int argc为变量个数 
//char *argv[]字符串指针数组,存储的是第i个命令行参数的字符串首地址
int main(int argc,char *argv[]) 
//char*env[] 环境变量
int main(int argc,char *argv[],char*env[])

注意:

1.argc默认值是1,例传入3个参数 argc = 4

2.argv[索引值]来获取其存储的内容,索引从0开始,默认argv[0]存储的是执行程序的全局路径,例:argv[0] =000001C13F39D6C0

3.env[0],存放的是环境变量。

4.2参数实践

给main中传递实参的方法有三种:

1.解决方案资源管理器->项目->属性->调试->命令参数

2.项目右键->文件资源管理器中打开->x64->Debug->右键终端打开 进行配置

3.win+R ->cmd ->进入当前目录->进行操作即可


main中的参数详细说明:

int main(int argc,char *argv[])

  • argc(argument count)是参数个数(整数),不是 "存储值的地方";

  • argv(argument vector)是字符串数组 (更准确地说,是 char* argv[]char** argv),存储的是每个命令行参数的字符串首地址

  • argv 的最后一个元素(argv [argc])标准规定为 NULL,这是为了遍历的时候也可以不用 argc,靠判断是否为 NULL 终止。

    #include <iostream>
    using namespace std;

    int main(int argc, char* argv[]) {
    cout << "argc = " << argc << endl;
    for (int i = 0; i < argc; ++i) {
    // 输出:索引 + argv[i]的地址值 + 指向的字符串内容
    cout << "argv[" << i << "] = " << (void*)argv[i]
    << " 对应的字符串:" << argv[i] << endl;
    }
    return 0;
    }

复制代码
#include <iostream>

using namespace std;

int main(int argc,char *argv[],char *env[]) {

		// 不依赖 argc 的遍历方式
		int i = 0;
		while (argv[i] != NULL) {
			cout << "argv[" << i << "] = " << (void*)argv[i] << " → " << argv[i] << endl;
			i++;
		}
	return 0;
}

5.switch 高效的分支判断

switch很简单,需要注意几点

1.switch仅支持整形和枚举类型

2.case执行后要break;不然后续的都会执行。

复制代码
/*
switch(变量)语句
case xxx:
	break;
case xxx:
	break;
default:
	//前面条件都不满足执行;


1.仅支持整数和枚举
2.可以转换为整形/枚举类型的表达式

c++17以上
swith(auto play=GetPlay();play.Status())
等价于
auto play = GetPlay();
switch(play.Status())

*/

案例实现:

复制代码
/*
switch(变量)语句
case xxx:
	break;
case xxx:
	break;
default:
	//前面条件都不满足执行;


1.仅支持整数和枚举
2.可以转换为整形/枚举类型的表达式

c++17以上
swith(auto play=GetPlay();play.Status())
等价于
auto play = GetPlay();
switch(play.Status())

*/
#include <iostream>

using namespace std;

enum class Status
{
	PLAY,
	STOP,
	ERROR
};

int main() {
	//整数类型的switch使用
	int x{ 0 };
	cin >> x;
	switch (x) {
	case 0:
		cout << "cose 0\n";
		break;
	case 1:
		cout << "cose 1\n";
		break;
	case 2:
		cout << "cose 2\n";
		break;
	default:
		cout << "default = " << x << endl;
	}

	// 枚举类型的switch使用
	Status status{ Status::PLAY };
	switch (status) {
	case Status::PLAY:
		cout << "PLAY" << endl;
		break;
	case Status::STOP:
		cout << "STOP" << endl;
		break;
	case Status::ERROR:
		cout << "ERROR" << endl;
		break;
	default:
		cout << "未知输入" << endl;

	}

}

6.实践案例

1.简单日志信息的打出说明

复制代码
#include <iostream>

using namespace std;

/*
需求说明:
1.用户控制显示日志的最高级别
*/

enum class LogLevel
{
	DEBUG,
	INFO,
	ERROR,
	FATAL
};

int main(int argc,char *argv[]) {
	//用户传递用户的最低显示级别
	//debug<info<error<fatal

	//默认的级别是debug
	auto logLevel = LogLevel::DEBUG;
	if (argc > 1) {
		string levelstr = argv[1];
		if ("info" == levelstr) {
			logLevel = LogLevel::INFO;
		}else if ("error" == levelstr) {
			logLevel = LogLevel::ERROR;
		}else if ("fatal" == levelstr) {
			logLevel = LogLevel::FATAL;
		}
	}

	//测试日志1 debug
	{
		auto level = LogLevel::DEBUG;
		string context = "test log 1";
		if (level >= logLevel) {
			string levelstr = "debug";
			switch (level) {
			case LogLevel::INFO:
				levelstr = "info";
				break;
			case LogLevel::ERROR:
				levelstr = "error";
				break;
			case LogLevel::FATAL:
				levelstr = "fatal";
				break;
			}

			cout << levelstr << ":" << context << endl;
		}

	}
	//测试日志2 info
	{
		auto level = LogLevel::INFO;
		string context = "test log 2";
		if (level >= logLevel) {
			string levelstr = "debug";
			switch (level) {
			case LogLevel::INFO:
				levelstr = "info";
				break;
			case LogLevel::ERROR:
				levelstr = "error";
				break;
			case LogLevel::FATAL:
				levelstr = "fatal";
				break;
			}

			cout << levelstr << ":" << context << endl;
		}

	}
	//测试日志3 error
	{
		auto level = LogLevel::ERROR;
		string context = "test log 3";
		if (level >= logLevel) {
			string levelstr = "debug";
			switch (level) {
			case LogLevel::INFO:
				levelstr = "info";
				break;
			case LogLevel::ERROR:
				levelstr = "error";
				break;
			case LogLevel::FATAL:
				levelstr = "fatal";
				break;
			}

			cout << levelstr << ":" << context << endl;
		}

	}
	//测试日志4 fatal
	{
		auto level = LogLevel::FATAL;
		string context = "test log 4";
		if (level >= logLevel) {
			string levelstr = "debug";
			switch (level) {
			case LogLevel::INFO:
				levelstr = "info";
				break;
			case LogLevel::ERROR:
				levelstr = "error";
				break;
			case LogLevel::FATAL:
				levelstr = "fatal";
				break;
			}

			cout << levelstr << ":" << context << endl;
		}

	}
}

基于上述代码进行进一步实践优化,添加了两个宏和其相应的日志宏[LOG]:

复制代码
#include<iostream>
#include<string>


//核心宏定义
//获取当前文件路径
#define CURRENT_FILE_PATH __FILE__
//获取当前行号
#define CURRENT_LINE  __LINE__
//组合文件和行号的调用宏
#define LOG(level_str, content) \
    std::cout << "文件 = " << CURRENT_FILE_PATH << " 的第" << CURRENT_LINE << "行的日志信息:[" \
              << level_str << "]" << content << std::endl;

using namespace std;

//日志枚举等级
enum class LogLevel
{
	DEBUG,
	INFO,
	ERROR,
	FATAL
};

int main(int argc,char *argv[]) {
	auto loglevel = LogLevel::DEBUG;
	string context = "test log";
	if (argc > 1) {
		string levelstr = argv[1];
		if ("debug" == levelstr) {
			LOG(levelstr, context);
		}else if ("info" == levelstr) {
			LOG(levelstr, context);
		}else if ("error" == levelstr) {
			LOG(levelstr, context);
		}else if ("fatal" == levelstr) {
			LOG(levelstr, context);
		}
			
	}
}

总结:

本文是笔者在学习c++的时候,做的一些总结,用于自身的学习和记录笔记,希望能给志同道合的诸位伙伴给予帮助!!!

相关推荐
瑶光守护者2 小时前
【学习笔记】5G RedCap:智能回落5G NR驻留的接入策略
笔记·学习·5g
你想知道什么?2 小时前
Python基础篇(上) 学习笔记
笔记·python·学习
曼巴UE52 小时前
UE5 C++ 动态多播
java·开发语言
小小晓.2 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS2 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
steins_甲乙3 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
xian_wwq3 小时前
【学习笔记】可信数据空间的工程实现
笔记·学习
煤球王子3 小时前
学而时习之:C++中的异常处理2
c++
请一直在路上3 小时前
python文件打包成exe(虚拟环境打包,减少体积)
开发语言·python
浩瀚地学3 小时前
【Arcpy】入门学习笔记(五)-矢量数据
经验分享·笔记·python·arcgis·arcpy