C++杂项(更新中)

C++学习中的杂项知识点记录

此篇博客用于记录我自己在学习C++语法时遇到的各种不易分类的知识点。

编译链接模型

未完待续

对齐

C++ 结构体对齐详解_c++结构体对齐-CSDN博客

编译器中的数据类型所需空间如下:

#include<iostream>

int main() 
{
	std::cout << sizeof(int) << std::endl;
	std::cout << sizeof(char) << std::endl;
}

同样是包含2个char和1个int的结构体,书写顺序会使其占用不同的内存大小:

#include<iostream>

struct duiqi
{
	char a;
	char b;
	int x;
};

int main() 
{
	std::cout << sizeof(duiqi) << std::endl;
}


#include<iostream>

struct duiqi
{
	char a;
	int x;
	char b;
};

int main() 
{
	std::cout << sizeof(duiqi) << std::endl;
}

char类型

char与signed char, unsigned char的区别?_char和signed char的区别-CSDN博客

不同数据类型之间的比较

#include<iostream>

int main() 
{
	int x(-1);		//效果上等价于 int x = -1
	unsigned y = 3;		//unsigned 是 unsigned int 的简写
	std::cout << (x>y) << std::endl;
}

之所以-1比3大,是因为C++比较不同数据类型时,会把有符号的转化成无符号的,那么-1就会变成一个非常大的数字,这样确实就比3大了。

c++20为此专门给出了一套解决方案:std::cmp_equal, cmp_not_equal, cmp_less, cmp_greater, cmp_less_equal, cmp_greater_equal - cppreference.com

#include<iostream>

int main() 
{
	int x = -1;
	unsigned int y = 3;

	std::cout << std::_Cmp_greater(x, y);
}

const与指针

int x;

int* const p = &x;
const int* p = &x;
int const* p = &x;

const在*右边表示指针为常量,不能改变,以下情况会报错:

#include<iostream>

int main()
{
	int x;
	int* const p = &x;  //可以理解为const修饰的是p
	int y;
	p = &y;    //报错
}

const在*左边 (const int* p = &x 与 int const* p = &x 是等价的) 表示指针的解引用为常量,不能改变,以下情况会报错:

#include<iostream>

int main()
{
	int x;
	const int* p = &x;    //可以理解为const修饰的是int*
	*p = 3;    //报错
}

但以下情况没问题:

#include<iostream>

int main()
{
	int x;
	const int* p = &x;  //这里存在一个隐式的类型转换,&x是int*类型,而p是const int*类型
	x = 3;
}

异或

3个特性:

1.具有交换律,如:2^3 = 3^2

2.相同两数异或为零,即与自己异或为零,如:3^3 = 0

3.与零异或为自身,如:0^2 = 2

利用这些特性,在不借助临时变量的情况下,便可实现两数交换:

#include<iostream>

int main()
{
    int x = 2;
    int y = 3;

    x ^= y ^= x ^= y;
/*
1.  x = x^y;       x = 2^3   y = 3
2.  y = y^(2^3);   x = 2^3   y = 3^2^3 = 3^3^2 = 0^2 = 2
3.  x = x^2;       x = 2^3^2 = 2^2^3 = 0^3 = 3  y = 2
*/

    std::cout << x << std::endl;
    std::cout << y << std::endl;
}

这种操作存在一个必要前提,即变量 x 与 y 不能指向同一块内存区域。那么,究竟在何种情形下会出现 x、y 使用同一内存的情况呢?举例来说,当我们自行编写了一个用于交换两个元素的函数,并运用该函数对一个数组中的元素进行前后倒置处理时,若数组元素的个数为奇数,就会出现特殊状况。此时,位于数组中间位置的那个元素,在交换过程中实际上会与自身进行异或运算,最终的结果便是该元素的值变为零。

istringstream

#include <iostream>   
#include <sstream>   
using namespace std;
int main()
{
    istringstream istr("1 56.7");

    cout << istr.str() << endl;//直接输出字符串的数据 "1 56.7"   

    string str = istr.str();//函数str()返回一个字符串   
    cout << str << endl;

    int n;
    double d;

    //以空格为界,把istringstream中数据取出,应进行类型转换   
    istr >> n;//第一个数为整型数据,输出1   
    istr >> d;//第二个数位浮点数,输出56.7   
    cout << d << endl;
    cout << n << endl;

    cout << istr.str() << endl;

    //假设换下存储类型   
    istr >> d;//istringstream第一个数要自动变成浮点型,输出仍为1   
    istr >> n;//istringstream第二个数要自动变成整型,有数字的阶段,输出为56     
    cout << d << endl;
    cout << n << endl;
}

最后的d和n没按注释输出1和56,而是仍输出56.7和1的原因是:此时istr中的内容已经被读取完毕,istr中读指针已经到达末尾,之后的赋值操作不会成功。

基于范围的for循环

读取和写入的区别

尽量避免用此语法进行参数赋值

反例:

#include <iostream>
#include <vector>

using namespace std;

int main() 
{
    std::vector <int> x{ 1, 2, 3, 4, 5 };

    for (int num : x)
    {
        cout << num;
    }
    cout << endl;

    for (int num : x)
    {
        num = 9;
        cout << num;
    }
    cout << endl;

    for (int num : x)
    {
        cout << num;
    }
    cout << endl;
}

可以看出该语法可以输出vector中的值,但不能将vector中的值全部赋为9。

原因:num只是当前vector元素的副本,而不是当前vector元素本身。

更具体的原因如下:

C++ Insights

#include <iostream>
#include <vector>

using namespace std;

int main() {
    std::vector <int> x{ 1, 2, 3, 4, 5 };
    for (int num : x)
    {
        cout << num << endl;
    }
}
#include <iostream>
#include <vector>

using namespace std;

int main()
{
  std::vector<int, std::allocator<int> > x = std::vector<int, std::allocator<int> >{std::initializer_list<int>{1, 2, 3, 4, 5}, std::allocator<int>()};
  {
    std::vector<int, std::allocator<int> > & __range1 = x;
    __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __begin1 = __range1.begin();
    __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __end1 = __range1.end();
    for(; !__gnu_cxx::operator==(__begin1, __end1); __begin1.operator++()) {
      int num = __begin1.operator*();
      std::cout.operator<<(num).operator<<(std::endl);
    }
    
  }
  return 0;
}

__begin1.operator*()用于解引用迭代器,返回当前元素的值。

int num = __begin1.operator*();将当前的值赋给num,而num只是for循环中的临时变量,对num赋值并不等同于对当前元素赋值。

但是,如果将代码中的第二个 for (int num : x) 改为 for (int& num : x) 便可以进行参数赋值了

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    std::vector <int> x{ 1, 2, 3, 4, 5 };

    for (int num : x)
    {
        cout << num;
    }
    cout << endl;

    for (int& num : x)
    {
        num = 9;
        cout << num;
    }
    cout << endl;

    for (int num : x)
    {
        cout << num;
    }
    cout << endl;
}

原因如下:

此时,因为 “int& num = __begin1.operator*();”, 所以对num赋值就等同于对容器中的当前元素赋值。 

二维数据的读取

实践发现,如果我们想要用for ( : )来打印出一个二维数组中的数据,那么就必须在第一个for循环当中使用引用。就像下面这样:

#include<iostream>

int main()
{
    int x[2][3] = { {1,2,3} ,{4,5,6} };
    for (auto& i : x)    //必须使用&
    {
        for (auto j : i)    //可以使用&,也可以不使用&
        {
            std::cout << j << std::endl;
        }    
    }
}

不过为什么要这样呢?经过上文分析可知,这只是来读取的又没有要写入,为什么一定要加引用呢?而且为什么只要在第一个for循环处加引用就可以了?第二个加不加都无所谓。

如果我们强制把这个引用给去掉的话,那么编译器就会报出如下错误:

于是我再次把源码放入C++insight中,查看它到底是如何编译的:

好像还看不出来有什么问题。这样只能证明加引用是可以的,但不能解释为什么去掉引用就不行?

这个问题当时卡了我很久,于是我在csdn上提出了这个问题(使用c++11新特性for ( : ) 来遍历二维数组时,为啥要用引用?_编程语言-CSDN问答),不过感觉都是人机的回答,啥也不是。

后来我就仔细想了想,按照我自己上面的理论分析,能得出如下四个结论:

int i [3] = *__begin1;      //语法不合法
int (&i) [3] = *__begin1;   //语法合法
int j = *__begin2;          //语法合法
int& j = *__begin2;         //语法合法

那么问题的关键就在于int[3]与int之间的区别。于是我自己就问了大模型这俩之间有什么区别,导致一个可以引用,一个不可以引用:

真相大白。所以归根到底,其实不是引用的问题,而是一个数组不能被另一个数组赋值。

其实这个问题也有其他人遇到过,只是当时我没有看到这篇文章:C++中的范围for循环与数组引用解析-CSDN博客

谓词函数和比较器

比较器是一种特殊的谓词函数,它属于二元谓词的范畴。

定义

谓词函数是一个可调用对象(可以是普通函数、函数指针、函数对象或 Lambda 表达式),它接受一个或多个参数,并返回一个布尔值(通常是 bool 类型),用于表示某个条件是否成立。根据接受参数的数量,谓词函数可分为一元谓词和二元谓词:

  • 一元谓词:只接受一个参数,用于判断该参数是否满足特定条件。
  • 二元谓词:接受两个参数,用于比较这两个参数之间的关系。

特点

  • 返回布尔值:谓词函数的返回值类型必须能够隐式转换为 bool,通常直接返回 bool 类型,以明确表示条件是否满足。
  • 无副作用:理想情况下,谓词函数不应该修改传入的参数,也不应该产生其他外部可见的副作用,仅专注于判断条件。

常见应用场景

标准库算法中的使用
一元谓词的应用(如 std::remove_if
#include <iostream>
#include <vector>
#include <algorithm>

// 一元谓词函数,判断元素是否为偶数
bool isEven(int num) 
{
    return num % 2 == 0;
}

int main() 
{
    std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };

    // 使用 remove_if 移除所有偶数元素
    numbers.erase(std::remove_if(numbers.begin(), numbers.end(), isEven), numbers.end());
    
    for (int num : numbers) 
    {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

在上述代码中,isEven 是一个一元谓词函数,用于判断一个整数是否为偶数。std::remove_if 算法使用该谓词函数来移除 std::vector 中所有满足条件(即偶数)的元素。

二元谓词的应用(如 std::sort
#include <iostream>
#include <vector>
#include <algorithm>

// 二元谓词函数,按降序排序
bool greater(int a, int b) 
{
    return a > b;
}

int main() 
{
    std::vector<int> numbers = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };

    // 使用 sort 按降序排序
    std::sort(numbers.begin(), numbers.end(), greater);

    for (int num : numbers) 
    {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

这里的 greater 是一个二元谓词函数,用于比较两个整数的大小,指示 std::sort 算法按降序对 std::vector 进行排序。

容器操作中的使用

在 std::list 的 remove_if 等操作中,也会用到谓词函数。例如:

#include <iostream>
#include <list>

// 一元谓词函数,判断元素是否大于 5
bool isGreaterThanFive(int num) 
{
    return num > 5;
}

int main() 
{
    std::list<int> myList = { 1, 6, 3, 8, 4, 9 };

    // 使用 remove_if 移除所有大于 5 的元素
    myList.remove_if(isGreaterThanFive);

    for (int num : myList) 
    {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

在这个例子中,isGreaterThanFive 是一元谓词函数,std::list 的 remove_if 方法使用该谓词来移除列表中所有大于 5 的元素。

其他形式的谓词

除了普通函数,还可以使用函数对象和 Lambda 表达式作为谓词。

函数对象作为谓词
#include <iostream>
#include <vector>
#include <algorithm>

// 函数对象(仿函数)作为一元谓词
struct IsOdd {
    bool operator()(int num) const {
        return num % 2 != 0;
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // 使用函数对象作为谓词
    numbers.erase(std::remove_if(numbers.begin(), numbers.end(), IsOdd()), numbers.end());
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

std::remove_if(numbers.begin(), numbers.end(), isEven)std::remove_if 算法会将满足 isEven 条件(即偶数)的元素移动到向量的末尾,并返回一个指向新的逻辑末尾的迭代器。

numbers.erase(..., numbers.end()):使用 erase 方法删除从新的逻辑末尾到向量实际末尾的所有元素。

Lambda 表达式作为谓词
#include <list>
#include <iostream>

// 打印List中的元素
void printList(const std::list<int>& List)
{
    for (int num : List)
    {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

int main()
{
    std::list<int> List = { 1, 100, 2, 3, 10, 1, 11, -1, 12 };

    size_t count = List.remove_if([](int n) { return n > 10; });
    std::cout << "移除了 " << count << " 个大于 10 的元素\n";

    printList(List);
}

函数对象和 Lambda 表达式提供了更灵活的方式来定义谓词,特别是 Lambda 表达式可以在需要的地方直接定义,无需额外定义函数或类。

any

std::any 是 C++17 引入的一个类型安全的任意类型容器,它可以存储任意类型的值,并且在需要时进行类型检查和类型转换。

示例:

#include <iostream>
#include <any>

int main() 
{
    std::any value = 42;  // 存储一个 int 类型的值
    if (value.type() == typeid(int))
    {
        std::cout << std::any_cast<int>(value) << std::endl;
    }
}

#include <iostream>
#include <vector>
#include <any>

int main() 
{
    std::vector<std::any> anyVector;
    anyVector.push_back(10);
    anyVector.push_back(3.14);
    anyVector.push_back(std::string("Hello"));

    for (const auto& item : anyVector) 
    {
        if (item.type() == typeid(int)) 
        {
            std::cout << std::any_cast<int>(item) << " ";
        }
        else if (item.type() == typeid(double)) 
        {
            std::cout << std::any_cast<double>(item) << " ";
        }
        else if (item.type() == typeid(std::string)) 
        {
            std::cout << std::any_cast<std::string>(item) << " ";
        }
    }
    std::cout << std::endl;
}

疑问

达夫设备

达夫设备的初衷是为了缩短循环体时间,但实际测试不能缩短?????

#include<iostream>
#include<vector>
#include<chrono>

int main()
{
	constexpr int count = 10000;	//恰好是8的倍数
	std::vector <int> buffer(count);

	for (const int i : buffer)
	{
		buffer[i] = rand() % 1000;    //伪随机,每次运行结果一样
		//std::cout << buffer[i] << std::endl;
	}

	int max = buffer[0];

	// 计时开始时间点
	// chrone 中常用的时钟类:
	// - std::chrono::high_resolution_clock
	// - std::chrono::system_clock
	// - std::chrono::steady_clock
	// 三种时钟类有一些区别,其中 high_resolution_clock 精度最高
	auto start = std::chrono::high_resolution_clock::now();

	for (int i = 0; i < count; ++i)
	{
		max = (max > buffer[i]) ? max : buffer[i];
	}

	/*for (int i = 0; i < count; i+=8)
	{
		max = (max > buffer[i]) ? max : buffer[i];
		max = (max > buffer[i + 1]) ? max : buffer[i + 1];
		max = (max > buffer[i + 2]) ? max : buffer[i + 2];
		max = (max > buffer[i + 3]) ? max : buffer[i + 3];
		max = (max > buffer[i + 4]) ? max : buffer[i + 4];
		max = (max > buffer[i + 5]) ? max : buffer[i + 5];
		max = (max > buffer[i + 6]) ? max : buffer[i + 6];
		max = (max > buffer[i + 7]) ? max : buffer[i + 7];
	}*/

	auto end = std::chrono::high_resolution_clock::now();
	// 计算运行时间, 时间单位:
	// - std::chrono::seconds
	// - std::chrono::milliseconds
	// - std::chrono::microseconds
	// - std::chrono::nanoseconds
	auto duration = std::chrono::duration_cast<std::chrono::nanoseconds> (end - start);
	
	std::cout << "最大值 : " << max << std::endl;
	std::cout << "查找时间 : " << duration.count() << " 纳秒" << std::endl;
#include<iostream>
#include<vector>
#include<chrono>

int main()
{
	constexpr int count = 10000;	//恰好是8的倍数
	std::vector <int> buffer(count);

	for (const int i : buffer)
	{
		buffer[i] = rand() % 1000;    //伪随机,每次运行结果一样
		//std::cout << buffer[i] << std::endl;
	}

	int max = buffer[0];
	auto ptr = buffer.begin();

	auto start = std::chrono::high_resolution_clock::now();

	/*for (int i = 0; i < count; ++i)
	{
		if (max < *ptr)	max = *ptr;	ptr++;
	}*/

	for (int i = 0; i < count; i+=8)
	{
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
		if (max < *ptr)	max = *ptr;	ptr++;
	}

	auto end = std::chrono::high_resolution_clock::now();
	auto duration = std::chrono::duration_cast<std::chrono::nanoseconds> (end - start);

	std::cout << "最大值 : " << max << std::endl;
	std::cout << "查找时间 : " << duration.count() << " 纳秒" << std::endl;
}

 原因很可能是这里的赋值根本没成功:

 具体参考上文:基于范围的for循环未完待续

main函数参数

详解main函数参数argc、argv及如何传参_argv[1]-CSDN博客

为啥可以直接使用如下方式打印出char* argv[]类型数据中的值???

#include <stdio.h>
 
int main(int argc, char *argv[]) {
    printf("%d\n", argc);
    
    for (int i = 0; i < argc; i++) {
        printf(" %d: %s\n", i, argv[i]);
    }
    
    return 0;
}

char* argv[]是个数组,数组中每个元素是 char*类型的,按理说argv[i]打印出的应该是个地址(指向一个char)啊???但实际却直接打印出字符串

下面同理:

#include<iostream>
#include <stdio.h>

int main() {

    std::string s = "program";
    const char* args[] = { s.c_str(), "arg1", "arg2", NULL };

    int argc = sizeof(args) / sizeof(args[0]) - 1; // 排除最后的 NULL

    for (int i = 0; i < argc; i++) {
        printf("args[%d] = %s\n", i, args[i]);
        std::cout << args[i] << std::endl;
    }

    return 0;
}

可能的原因:

自引用结构

static和inline的意义作用

#include <iostream>   
 
using namespace std;

struct Str
{
	static Str x;
};

inline Str Str::x;

int main() 
{
	Str ml; 
	std::cout << &(ml.x) << std::endl;
}

附录

配置启动项目

有时候新建了一个项目,点击运行按钮后依然运行之前的项目,可按如下配置来运行新项目:

进程未正常停止

报错提示的主要内容为:无法打开 E:\VS项目\Project1\Debug\Project2.exe 进行写入

导致这一情况发生的原因是上次运行代码对应的进程没有停止,可能是调试模式没有关闭成功,或者是控制台窗口没有关闭,或者是程序运行完后虽然关闭了控制台窗口,但是没有成功停止进程。

解决方法是在任务管理器中用Ctrl+F搜索这个程序并结束它。

修改VS编译器的c++标准

注意,这种修改只对一个解决方案中的指定项目有效。如果一个解决方案中包括多个项目,则需要多次修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值