此篇博客用于记录我自己在学习C++语法时遇到的各种不易分类的知识点。
编译链接模型
未完待续
对齐
编译器中的数据类型所需空间如下:
#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元素本身。
更具体的原因如下:
#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++标准
注意,这种修改只对一个解决方案中的指定项目有效。如果一个解决方案中包括多个项目,则需要多次修改。