C++开发过程中遇见的知识点记录

1.模版template使用报错问题

        下午开发代码,看见一处.cpp文件A代码直接拿了另一个.cpp文件B的类成员使用,但没找到B文件的.h文件中有该变量的声明(后面查的过程发现是头文件一个宏定义展开定义的该变量),然后我就好奇了:直接在.cpp中定义的变量也能直接跨文件使用吗?便开始了网上找资料之路,结果一步步延伸到了代码模板的使用问题上,因为之前写过几次模板函数,费老大劲写完了,结果编译、运行啥的老出问题,当时也没解决,现在就趁着这个机会看了下学习学习。(只是浅浅理解,有些地方表述不准确,欢迎指正。)

1.c++中.h和.cpp的关系

       最初c++也是只有一种类型文件cpp,但使用过程中一些人感觉把声明啥的单独拎出来放在一个文件里,作为接口啥的看着直观清晰一些,然后.h文件就应景而生了;但其实到现在,主流上又有人感觉两种文件使用不方便,就又有只使用一种类型文件的趋势,比如只保留.h文件,然后使用inline实现各个函数;再比如c++20的modules。

        代码的整个编译过程,大致可以分为三步:预处理编译链接

        预编译:将源代码当中的#号开头的预处理命令、模板或者还有一些其他的东西进行展开。比如像.cpp中的include导入,其实就是把头文件中的代码声明复制过来到自己文件中。这里值得一提的是,编译器眼中的.h、.cpp后缀文件都一个样,没什么区别,把#include中的.h后缀换成.txt也一样能行。

        编译:分离式编译,对每一个处理过的.cpp文件进行编译生成过程文件,每个文件编译都是单独的,自己玩,不牵涉其他文件。

        链接:把上一步每个单独编译出的文件,进行处理合并,形成一个完整的dll或exe。

2.模版函数的实现需放在.h文件的原因

        现有如下场景,B.h声明一个模板函数,B.cpp实现该模板,然后A.cpp调用该模板:

//A文件-------A.cpp
#include "B.h"
int main(void)
{
	defClass testClass;
	int a = 123;
	int b=testClass.funcAdd<int>(a);
	return 0;
}

//B文件-------B.h
class defClass{
public:
    defClass();
    ~defClass();
    template<typename T> 
    T funcAdd(T a);
};

//B文件-------B.cpp
template<typename T>
T defClass::funcAdd(T a) {
	return a + 100;
}

(1)模板定义在.cpp中

预处理:根据前面的了解,文件主体A.cpp经过预处理后变为如下结构:

//A文件预处理展开后-----------A.cpp
class defClass:public parClass {
public:
    defClass();
    ~defClass();
    template<typename T> 
    T funcAdd(T a);
};
int main(void)
{
	defClass testClass;
	int a = 123;
	int b=testClass.funcAdd<int>(a);
	return 0;
}

编译:A.cpp经过预处理后,进行文件编译,此时编译器发现main函数主体使用了模板函数,需要模板函数实例化(把模板T类型换为实际的使用类型int),但整个文件内只有模板声明而没有定义,于是就把希望寄托于链接阶段处理。

链接:A.cpp去B.cpp中找模板函数的int类型实例化,但是呢,B.cpp只有模板函数定义而没有int的实例化函数(c++有规定,没有实际使用的模板函数,不进行实例化,所以在B.cpp的编译阶段并未进行该模板实例化),因此报错“无法解析的外部命令”。

(2)模板定义在.h中

现在把模板函数的定义放在.h中进行实现:

预处理:根据前面的了解,文件主体A.cpp经过预处理后变为如下结构:

//A文件预处理展开后-----------A.cpp
class defClass:public parClass {
public:
    defClass();
    ~defClass();
    template<typename T> 
    T funcAdd(T a);
};
template<typename T>
T defClass::funcAdd(T a) {
	return a + 100;
}
int main(void)
{
	defClass testClass;
	int a = 123;
	int b=testClass.funcAdd<int>(a);
	return 0;
}

编译:A.cpp经过预处理后,进行文件编译,此时编译器发现main函数主体使用了模板函数,需要模板函数实例化(把模板T类型换为实际的使用类型int),文件内找到了函数定义,于是正常对模板函数以int类型实例化。

链接:A.cpp和B.cpp编译的结果处理链接在一块。代码正确。

2.vs生成的dll,断点无法命中调试

今天写代码,从别人那拿过来一个dll后,发现一直无法命中断点进行调试,现象图如下:

 然后各种懵的去试、问度娘,但一直没找到想用的 ,蹉跎了将近俩小时,忽然发现了一个生成时的配置项:

这个配置项配置的“否”,改为如上图选项就可以打上断点了。 

3.代码string值异常乱码导致软件崩溃的问题

在使用LoadLibrary进行显式加载dll库遇见的该问题。碰见情形:

(1)基类初始化时,其父类的的构造函数有string类型值,结果代码崩溃,断点调试发现string值为乱码,就在构造函数这里挂掉了。求助网上说是字符集的问题需要调用程序和调用的dll字符集一致。(我的是多字符集)

 修改该字符集保持一致后,发现还是不行,无奈之下就把参数改为int类型了。

(2)代码继续调试,结果代码再次崩溃,断点调试,发现是函数里面的string字符串操作乱码导致的,很明显还是编码的问题,但是明明都改了。

上面基本可以定位是项目配置的问题,就在我打算放弃复制一个别的可以用的工程,然后把写的.cpp、.h文件放进去时,突然灵光一现,打开了工程的.vcxproj文件,一看发现其中的字符集配置仍旧是Unicode编码,修改为MultiByte(多字符集)后,问题解决。

20240401补充: 

今天新建了个新的项目,没想到呀,没想到呀,再次碰见了这个string读取失败的问题,显示“读取字符串字符时出错”,下面用到该字符串时就会软件崩溃了:

有了前车之鉴, 第一时间查看字符集配置,看了个遍没问题。然后就开始一个个比对以前的项目和这个新建的项目配置项区别,改了不少地方,不过最后测试觉得是这两个地方影响的,一处预定义、一处代码生成的运行库:

MT,MTd,MD,MDd详解解决 MSVCRTD.LIB和LIBCMTD.LIB冲突_libcmtd.lib msvcrtd.lib-CSDN博客 放个详细的链接,上面说了该配置项(运行库)的对代码编译的影响,根据介绍,个人感觉导致该现象的原因是(个人猜测,可能不正确,欢迎指正):

        (1)之前项目用的MD选项,项目连接MSVCRT.lib库,这是个导入库,对应动态库为MSVCRT.dll

        (2)新建的项目使用MDd选项,多线程动态调试库,连接MSVCRTD.lib库,对应动态库为MSVCRTD.dll

        (3)两个项目间连接的MSVCRT.dll、MSVCRTD.dll不一样导致string运行(编译时不报错)时数据无法读取。

4.迭代器的不同类型、std::transform函数

(1)std::transform函数

最初是打算将一个vector转化为map的,感觉for遍历写着有点low,就去网上查资料,发现能使用std::transform函数进行转化,最初写了一版(错误写法)如下:

//std::transform()在如下头文件
#include <algorithm>

vector<string> words;
multimap<string,bool> mapWords;

/*
words.begin(), words.end():传入words的需要转化首末迭代器。
mapWords.end():传入map的尾迭代器,作为插入的起始位置
[].... :lamdba表达式用来返回处理后数据插入map里。
*/
std::transform(words.begin(), words.end(), mapWords.end(),
    [](const string& word) ->  pair<string, bool>  {
    return make_pair(word, false);
});

上面的写法编译一直报错,类型不匹配 。然后边查资料,边做了如下尝试:

vector<string> words={"123","456"};
vector<string> words2;

/*这种也是一个错误写法,报错,数组越界。*/
std::transform(words.begin(), words.end(), words2,
   [](const string& word){
   return word+"777";
});

/*将words2空间初始化后,转化成功*/
vector<string> words2(2);//初始化空间大小
/*下面操作将输出words2={"123777","456777"}*/
std::transform(words.begin(), words.end(), words2,
   [](const string& word){
   return word+"777";
});

 然后vector转化map失败就很显然了,pair不能直接转化map,但是就纳闷了,查资料过程中在某个网页见过这种写法呀,然后又去巴拉历史记录,果然有东西漏写了 ----inserter()函数。修改后代码如下:


vector<string> words={"123","456"};
multimap<string,bool> mapWords;
/*
words.begin(), words.end():转化首末迭代器
inserter(mapWords,mapWords.end()):插入型迭代器,该迭代器类型表明将要在数据结构类型的尾部进行插入。
[]..... :转化数据结构。

如下操作将输出mapWords=[{"123",false},{"456",false}]
*/
std::transform(words.begin(), words.end(), inserter(mapWords,mapWords.end()),
   [](const string& word) ->  pair<string, bool>  {
   return make_pair(word, false);
});

 (2)迭代器类型

上面提到的inserter()函数是定义了一个插入型迭代器,类似的还有front_inserter()、back_inserter()。在transform使用该类型迭代器表明将把回调函数的值,作为一个元素插入数据结构之中,即调用push_back函数。

而经常使用的begin()、end()函数,是一种赋值型迭代器,在transfrom函数中表明将要对相应数据结构赋值,即调用=(赋值)操作,所以上面对未初始化控件的vector赋值数据,产生了数据越界错误。

除了上面的迭代器,还有一些别的:

插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素
流迭代器:这些迭代器被绑定到输入或输出上,可用来遍历所有关联的IO流
反向迭代器:这些迭代器向后而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器
移动迭代器:这些专用的迭代器不是拷贝其中的元素,而是移动它们。

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨菜的阿豪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值