C++学习容易错的

1. 字符型指针与字符数组在使用中异同,如:

(1)

char str[81] = "abcde", *pstr = "abcde";//正确  

(2)

char str[81], *pstr;  
str = "abcde";//错误  
pstr = "abcde";//正确  

str是数组的首地址,是常量指针,是不能出现赋值符号左边,从语法上是错误的。
(3)

char str[81], *pstr;  
cin >> str;//正确  
cin >> pstr;//错误  

pstr指针变量所指的对象没有初始化,直接在输入流中给赋值,是错误的。

2 指针数组

定义格式:
类型 *数组名[元素个数]

int *ptr[5];//定义5个int型的指针数组
//定义指向多个字符串的指针数组
char *str[]={“Basic”,"Fortran","C++","Java"};

3 volatile 关键字

主要用到线程之间的通讯中。
举例说明:

volatile int i=10; 
int j = i; 
... 
int k = i; 

volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。

volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。

其中编译器编译优化是:
由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

4 字符串和数字的相互转化

VC中数字与字符串转换方法 网址:(http://www.cppblog.com/tbyxyz/articles/str2num.html)

5 C++字符串之间转化——Unicode字符集

原文地址C++字符串之间转化——Unicode字符集
原文地址C++: std::string 与 Unicode 如何结合
原文地址 C++中string和string.h以及cstring,CString的作用和区别

LPCTSTR不是一个类型,而是两种类型:LPCSTRLPCWSTR其中之一。会根据你当前程序是否使用UNICODE字符集来变成那二者之一。如果使用UNICODE字符集,则LPCTSTR = LPCWSTR,否则LPCTSTR = LPCSTR
标准库的std::string转换成LPCSTR很简单:直接调用c_str()即可。例:

std::string a="abc";
LPCSTR str = a.c_str();

标准库还有一个wstring,代表宽字符的stringstd::wstring转换成LPCWSTR也一样很简单:

std::wstring a = L"abc";
LPCWSTR str = a.c_str();

如果要是std::string转换成LPCWSTR或者std::wstring转换成LPCSTR那就比较麻烦了,需要调用MultiByteToWideCharWideCharToMultiByte进行字符集之间的转换。不过大多数时候不需要这种交叉转换,一个程序一般只搜索会使用一种字符集。

6 C++申请动态二维数组

原文地址: 如何在C/C++中动态分配二维数组,链接:http://jingyan.baidu.com/article/11c17a2c765763f446e39dc1.html

7 error lnk2019 lnk1120

常见错误是在.h文件中定义了函数,但是在.cpp文件中忘了在函数头上添加类的名字
如:A.h中的CA类中定义了 void a();
但是在A.cpp中函数写成了void a(){}
正确的写法应该是void CA::a(){}

8 error c2143 两个头文件相互引用

两个头文件相互包含会导致超前引用的问题,所谓超前引用是指一个类型在定义之前就被用来定义变量和声明函数。发生这种情况是无法编译通过的,不过可以采取一些手段解决该问题
超前引用导致的错误有以下几种处理办法:
1) 使用类声明
在超前引用一个类之前,首先用一个特殊的语句说明该标识符是一个类名,即将被超前引用。其使用方法是:
a) 用class ClassB;声明即将超前引用的类名
b) 定义class ClassA
c) 定义class ClassB;
d) 编制两个类的实现代码。
上述方法适用于所有代码在同一个文件中,一般情况下,ClassA和ClassB分别有自己的头文件和cpp文件,这种
方法需要演变成:
a) 分别定义ClassA和ClassB,并在cpp文件中实现之
b) 在两个头文件的开头分别用class ClassB;和class ClassA;声明对方
c) 在两个cpp文件中分别包含另外一个类的头文件
NOTE:这种方法切记不可使用类名来定义变量和函数的变量参数,只可用来定义引用或者指针。
2) 使用全局变量
由于全局变量可以避免超前引用,不用赘述。我的习惯是,把类对象的extern语句加在该类头文件的最后,大家喜欢怎样写那都没有什么大问题,关键是保证不要在头文件中胡乱包含。
3) 使用基类指针。
这种方法是在引用超前引用类的地方一律用基类指针。而一般情况下,两个互相引用的类并不涉及其基类,因此不会造成超前引用。

9 CString 的GetBuffer函数

注意能用CString的地方就不要使用其他如LPCTSTR、LPSTR等,这写首先转化起来比较繁琐,而且出错!如果使用MFC进行编程,那么就使用自带的CString吧!
原文地址:CString 的GetBuffer函数
这是一个非常容易被用错的函数,主要可能是由于大家对它的功能不太了解。其实点破的话,也不是那么深奥。
这个函数是为一个CString对象重新获取其内部字符缓冲区的指针,返回的LPTSTR为非const的,从而允许直接修改CString中的内容! 如果nMinBufLength 比当前buffer大,那么将调用ReleaseBuffer函数去释放当前的Buffer,用一个被请求的大小去覆盖这个bufferGetBuffer(int size)是用来返回一个你所指定大小可写内存的成员方法。它和被重载的操作符LPCTSTR还是有点本质区别的,LPCTSTR是直接返回一个只读内存 的指针,而GetBuffer则是返回一个可以供调用者写入的内存,并且,你可以给定大小。下面是个简单的,但也是非常典型的例子:

int readFile(CString& str, const CString& strPathName)
  {
  FILE* fp = fopen(strPathName, "r"); // 打开文件
  fseek(fp, 0, SEEK_END);
  int nLen = ftell(fp); // 获得文件长度
  fseek(fp, 0, SEEK_SET); // 重置读指针
  char* psz = str.GetBuffer(nLen);
  fread(psz, sizeof(char), nLen, fp); //读文件内容
  str.ReleaseBuffer(); //千万不能缺少
  fclose(fp);
  }

   上面的函数是GetBuffer函数最典型的用法了,其实它就相当于申请一块nLen大小的内存,只不过,这块内存是被引用在CString对象的内部 而已,这是非常有效的一种用法,如果不直接用GetBuffer函数来申请的话,那么你必须用new操作符(或者malloc()函数)在CString的外部申请,然后再将申请的内存拷贝到CString对象中,显然这是一个非常冗余的操作,会使你函数的效率大大下降。
  ReleaseBuffer函数是用来告诉 CString对象,你的GetBuffer所引用的内存已经使用完毕,现在必须对它进行封口,否则CString将不会知道它现在所包含的字符串的长 度,所以在使用完GetBuffer之后,必须立即调用ReleaseBuffer函数重置CString的内部属性,其实也就是头部信息。
GetBuffer的第一种用法,也是最简单的一种,不用给它传递参数,它使用默认值0,意思是:“给我这个字符串的指针,我保证不加长它”。当你调用 ReleaseBuffer 时,字符串的实际长度会被重新计算,然后存入 CString 对象中。
必须强调一点,在 GetBufferReleaseBuffer之间这个范围,一定不能使用你要操作的这个缓冲的 CString对象的任何方法。因为 ReleaseBuffer被调用之前,该CString对象的完整性得不到保障。
假设你想增加字符串的长度,你首先要知道这个字符串可能会有多长,好比是声明字符串数组的时候用:char buffer[1024]; 表示 1024 个字符空间足以让你做任何想做得事情。
CString 中与之意义相等的表示法:LPTSTR p = s.GetBuffer(1024); 调用这个函数后,你不仅获得了字符串缓冲区的指针,而且同时还获得了长度至少为 1024 个字符的空间(注意,我说的是“字符”,而不是“字节”,因为 CString是以隐含方式感知 Unicode 的)。
另外一个常见的错误是:既然固定大小的内存不工作,那么就采用动态分配字节,这种做法弊端更大:

int len = lstrlen(parm1) + 13 lstrlen(parm2) + 10 + 100;
char * buffer = new char[len];
sprintf(buffer, \"%s is equal to %s, valid data\", parm1, parm2);
CString s = buffer;
......
delete [] buffer;

需要注意sprintf例子都不是 Unicode 就绪的,尽管你可以使用 tsprintf 以及用 _T() 来包围格式化字符串,但是基本 思路仍然是在走弯路,这这样很容易出错。它可以能被简单地写成:

CString s;
s.Format(_T(\"%s is equal to %s, valid data\"), parm1, parm2);

10 交换两个变量的值,不使用第三个变量的四种方法

原文地址:http://blog.youkuaiyun.com/kangkermit/article/details/21371159
通常我们的做法是(尤其是在学习阶段):定义一个新的变量,借助它完成交换。代码如下:
int a,b;
a=10; b=15;
int t;
t=a; a=b; b=t;
这种算法易于理解,特别适合帮助初学者了解计算机程序的特点,是赋值语句的经典应用。在实际软件开发当中,此算法简单明了,不会产生歧义,便于程序员之间的交流,一般情况下碰到交换变量值的问题,都应采用此算法(以下称为标准算法)。

上面的算法最大的缺点就是需要借助一个临时变量。那么不借助临时变量可以实现交换吗?答案是肯定的!这里我们可以用三种算法来实现:1)算术运算;2)指针地址操作;3)位运算;4)栈实现。

1) 算术运算
int a,b;
a=10;b=12;
a=b-a; //a=2;b=12
b=b-a; //a=2;b=10
a=b+a; //a=10;b=10
它的原理是:把a、b看做数轴上的点,围绕两点间的距离来进行计算。
具体过程:第一句“a=b-a”求出ab两点的距离,并且将其保存在a中;第二句“b=b-a”求出a到原点的距离(b到原点的距离与ab两点距离之差),并且将其保存在b中;第三句“a=b+a”求出b到原点的距离(a到原点距离与ab两点距离之和),并且将其保存在a中。完成交换。
此算法与标准算法相比,多了三个计算的过程,但是没有借助临时变量。(以下称为算术算法)
缺点:是只能用于数字类型,字符串之类的就不可以了。a+b有可能溢出(超出int的范围),溢出是相对的, +了溢出了,-回来不就好了,所以溢出不溢出没关系,就是不安全。

2) 指针地址操作
因为对地址的操作实际上进行的是整数运算,比如:两个地址相减得到一个整数,表示两个变量在内存中的储存位置隔了多少个字节;地址和一个整数相加即“a+10”表示以a为基地址的在a后10个a类数据单元的地址。所以理论上可以通过和算术算法类似的运算来完成地址的交换,从而达到交换变量的目的。即:
int *a,*b; //假设
*a=new int(10);
*b=new int(20); //&a=0x00001000h,&b=0x00001200h
a=(int*)(b-a); //&a=0x00000200h,&b=0x00001200h
b=(int*)(b-a); //&a=0x00000200h,&b=0x00001000h
a=(int*)(b+int(a)); //&a=0x00001200h,&b=0x00001000h
通过以上运算a、b的地址真的已经完成了交换,且a指向了原先b指向的值,b指向原先a指向的值了吗?上面的代码可以通过编译,但是执行结果却令人匪夷所思!原因何在?
首先必须了解,操作系统把内存分为几个区域:系统代码/数据区、应用程序代码/数据区、堆栈区、全局数据区等等。在编译源程序时,常量、全局变量等都放入全局数据区,局部变量、动态变量则放入堆栈区。这样当算法执行到“a=(int*)(b-a)”时,a的值并不是0x00000200h,而是要加上变量a所在内存区的基地址,实际的结果是:0x008f0200h,其中0x008f即为基地址,0200即为a在该内存区的位移。它是由编译器自动添加的。因此导致以后的地址计算均不正确,使得a,b指向所在区的其他内存单元。再次,地址运算不能出现负数,即当a的地址大于b的地址时,b-a<0,系统自动采用补码的形式表示负的位移,由此会产生错误,导致与前面同样的结果。
有办法解决吗?当然!以下是改进的算法:

if(a<b)
{
a=(int*)(b-a);
b=(int*)(b-(int(a)&0x0000ffff));
a=(int*)(b+(int(a)&0x0000ffff));
}
else
{
b=(int*)(a-b);
a=(int*)(a-(int(b)&0x0000ffff));
b=(int*)(a+(int(b)&0x0000ffff));
}

算法做的最大改进就是采用位运算中的与运算“int(a)&0x0000ffff”,因为地址中高16位为段地址,后16位为位移地址,将它和0x0000ffff进行与运算后,段地址被屏蔽,只保留位移地址。这样就原始算法吻合,从而得到正确的结果。
此算法同样没有使用第三变量就完成了值的交换,与算术算法比较它显得不好理解,但是它有它的优点即在交换很大的数据类型时,它的执行速度比算术算法快。因为它交换的时地址,而变量值在内存中是没有移动过的。(以下称为地址算法)

3) 位运算
int a=10,b=12; //a=1010^b=1100;
a=a^b; //a=0110^b=1100;
b=a^b; //a=0110^b=1010;
a=a^b; //a=1100=12;b=1010;
此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。

4)栈实现。不多解释了,栈和相关函数定义省去。
int exchange(int x,int y)
{
stack S;
push(S,x);
push(S,y);
x=pop(S);
y=pop(S);
}

以上算法均实现了不借助其他变量来完成两个变量值的交换,相比较而言算术算法和位算法计算量相当,地址算法中计算较复杂,却可以很轻松的实现大类型(比如自定义的类或结构)的交换,而前两种只能进行整形数据的交换(理论上重载“^”运算符,也可以实现任意结构的交换)。
介绍这三种算法并不是要应用到实践当中,而是为了探讨技术,展示程序设计的魅力。从中可以看出,数学中的小技巧对程序设计而言具有相当的影响力,运用得当会有意想不到的神奇效果。而从实际的软件开发看,标准算法无疑是最好的,能够解决任意类型的交换问题。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值