C/C++遇到的坑(持续更新,每日更新两篇,11/24)

问题摘录自《从缺陷中学习C/C++》

1.基础问题

1.1 运算符优先级

//to get 2*n+1
int func(int n)
{
    
    
	return n<<1 + 1;
}
  • 后果

上述代码中的函数func本意是期望计算2n+1,但程序实际运行结果是4n。

  • 分析

这段代码使用左移1位来代替乘以2的运算,是很好的方法,但是编程者弄错了运算符“<<”和“+”的优先级。C/C++语言规定运算符“+”的优先级高于运算符“<<”,因此,上述语句“return n<<1+1”等同于“returnn<<(1+1)”,所以,会先进行加法运算,再进行左移运算,得到结果4*n

  • 处理解决
int func(int n)
{
    
    
	return (n<<1)+1;
}

2.编译问题

3.库函数问题

4.文件处理问题

5.类和对象问题

1) 对象的浅复制

class IntList 
{
    
     
public:   
static const int SIZE = 10;  
int *Items;   
int numItems;   
int arraySize; 

public:   
IntList() 
{
    
        
Items = new int[SIZE];    
numItems = 0;    
arraySize = SIZE;  
}   

~IntList() 
{
    
        
cout << "delete" << endl;    
delete Items;  
}
};

void do_some_process(IntList *list) 
{
    
      IntList tmp;  
// do something else
 *list = tmp;
 }

int main() 
{
    
      
IntList list;  
cout << "Items : " << *list.Items << endl; 
do_some_process(&list);  
cout << "Items : " << list.Items[0] << endl;  
return 0;
}
  • 问题

程序运行时出现段错误

  • 分析

1)如果类中没有对“=”操作符重载或没有提供赋值构造函数,那么对象间的复制只是浅复制(shallow copy),浅复制的意思就是C++只会对对象中的每个成员使用赋值运算符。当类很简单的时候(如没有动态分配内存的情况),浅复制不会出问题。但是如果其中一个类成员变量为指针变量,并且指向动态分配的空间,那么在赋值之后,被赋值的对象的指针变量将会指向原对象动态分配的空间,因此原对象被析构之后,被赋值对象的指针就变成悬挂指针。
2)上述代码中,do_some_process 函数中两个对象之间的赋值操作*list = tmp只是将对象tmp的内存内容复制给对象list,因此,两个对象的指针成员Item都指向了同一片地址。当do_some_process函数退出时,因tmp是函数内临时对象,它的生命周期也结束,这时会调用析构函数释放掉tmp所占用的内存,包括Item所指向的地址,导致list成员Item成为一个野指针,一旦操作这个野指针就可能会造成程序崩溃。

  • 改正方法
// 在类IntList中重载“=”操作符,如下
IntList & operator=(const IntList &l) 
{
    
      
this->numItems = l.numItems;  
this->arraySize = l.arraySize;  
memcpy(this->Items, l.Items, sizeof(int) * this->numItems);
}

2) 构造函数中的操作符重载造成死循环

  • 问题代码
class MyClass
{
    
     
private:   
int data; 

public:   
MyClass(void)
{
    
    
data = 0;
}   

MyClass(const MyClass &i_class)
{
    
        
cout << "construct MyClass" << endl;   
*this = i_class;  
}  

MyClass operator = (const MyClass &i_class)
{
    
       
Cout << "operate=" << endl;   
Data = i_class.data;  
return *this; 
}  

void put(const int value)
{
    
       
data = value; 
}  

int get(){
    
       
return data; 
}
};

int main()
{
    
      
cout << "main" << endl;  
MyClass first; 
first.put(10);  
MyClass second(first);  
Cout << "second get " << second.get() << endl;  
return 0;
}
  • 现象&后果

程序编译没有问题,但是执行时陷入死循环

  • Bug分析

1)在 C++中,如果一个函数返回值(非引用)时,会生成一个匿名的临时变量并将函数返回值赋值给匿名的临时变量;如果函数返回引用,则不会生成临时变量。例如:int func()函数的返回值为 int型,在执行 int n = func()语句时会首先生成一个匿名的临时变量,并将函数 func()的返回值赋值给匿名的临时变量,然后再将匿名的临时变量值赋值给 n。这里有两次赋值的操作。int &func()函数的返回为引用,在执行 int n = func()语句时,会把返回引用直接赋值给 n,这里只有一次赋值的操作。
2)在上段代码中,MyClass 类定义了两个构造函数和一个操作符重载函数,在构造函数MyClass(const MyClass &i_class)中,两个对象赋值时会调用操作符重载函数。调用操作符重载函数后会生成临时变量,并把操作符重载函数的返回值赋值给临时变量,这个过程会再次调用构造函数MyClass(const MyClass &i_class)和操作符重载函数……,从而导致反复调用构造函数及操作符重载函数,程序陷入死循环。

  • 解决方法

解决这个问题,只需把操作符重载函数的返回值类型改成返回引用类型即可。

class MyClass
{
    
     
private:   
int data; 

public:   
MyClass(void)
{
    
    
data = 0;
}   

MyClass(const MyClass &i_class)
{
    
        
Cout << "construct MyClass" << endl;   
*this = i_class;  
}  

MyClass& operator = (const MyClass &i_class)
{
    
       
Cout << "operate=" << endl;   
Data = i_class.data;  
return *this; 
}  

void put(const int value)
{
    
       
data = value; 
}  

int get(){
    
       
return data; 
}
};

int main()
{
    
      
cout << "main" << endl;  
MyClass first; 
first.put(10);  
MyClass second(first);  
cout << "second get " << second.get() << endl;  
return 0;
}

6.内存使用问题

7.多线程问题

8.性能问题

9.其他问题

9.1 中文截断成乱码

  • 代码
const int MAX_LEN = 1024*1024;
string line,str;
while(getline(cin,line))
{
    
    
	str = line;
	if(line.length>MAX_LEN)
	{
    
    
		str = line.substr(0,MAX_LEN-1);//截断
		std::cout<<str<<std::endl;
	}
}
  • 后果

上面代码的目的是对输入字符串做长度判断,当输入字符串过长时,会做截断处理。在程序运行时发现,当输入字符串中包含有汉字等宽字节字符时,截断操作可能会造成乱码。

  • 原因

上面代码中,当输入的数据的长度大于1MB时,会被截断成1MB大小。但如果输入数据使用UTF-8编码,这种截断方式就有可能造成乱码。UTF-8是一种不定长编码,通常情况下,英文字母、数字和普通字符占用一个字节,中文字符占用 3个字节。因此,若输入的数据有中文,且刚好从中文的3个字节中间截断,那么再以UTF-8对数据做解码时就会失败,从而造成乱码

  • 处理方案

截断后,对数据做编码识别,若非预期的编码或识别失败,说明有被截断的嫌疑,则往后多取一位或者往前少取一位,再进行识别直到成功为止。编码的识别比较复杂,可以使用第三方比较成熟的库,如著名的开源软件ICU库

  • 建议

涉及字符串截断的操作,要考虑数据来源的编码格式,尤其是像UTF-8这种非定长的编码。

10.其他知识补充:

1)C++中包含C的代码

#ifdef __cplusplus
extern "C"
{
    
    
#endif
	...
#ifdef __cplusplus
}
#endif

  • 如果将Lua作为C代码编译出来后又要在C++中使用,那么可以引入lua.hpp来替代lua.h,定义如下:
extern "C"{
    
    
#include "lua.h"
}

11.其他

1)C++:switch提示“控制传输跳过的实例化”

在这里插入图片描述

  • 解决方法:

解决方法很简单,将case下的语句加上"{}"即可,也就是写成

	switch (m_nCardType) {
    
    
	case MODBUS485_MASTER:{
    
    
			CModBus485MasterDlg MDlg;
			MDlg.DoModal();
			break;
			}
	default:
		break;
	}

2)定义自己的max和min函数

#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))

3)结构体前置申明未定义问题

误 1 error C2079: “ud_x”使用未定义的 struct“ud”

结构体前置申明时,定义的变量只能是指针类型 如 struct_x *x;

如果是对象则编译器无法判读结构体大小导致提示使用未定义。

上面都是个人了解,不保证完全准确,仅供参考。

猜你喜欢

转载自blog.csdn.net/weixin_43679037/article/details/120192314
今日推荐