C/C++容易出错的地方

2180阅读 0评论2016-06-10 wibnmo
分类:C/C++

1. C++中,内部和外部变量重名时,如何引用外部变量?
   #include
   using namespace std;
   
   int Vac = 2;
   int main() {
int Vac = 4;
::Vac++; //看这里,用外部变量时,在变量前加"::"
   }


2. x=x+1, x+=1, x++, 哪个效率高?
   x=x+1效率最低
   1) 读取右x地址
   2) x+1
   3) 读取左x地址
   4) 将右值传给左边的x(编译器并不认为左右x地址相同)
   x+=1其次
   1) 读取右x地址
   2) x+1
   3) 将得到的值传给x(因为x地址已读出,所以不用再读)
   x++效率最高
   1) 读取右x地址
   2) x自增1


3. 以下代码输出结果是?
   int i = 2;
   ++i*++i = ? // 先加后取,所以结果为16
   i++*i++ = ? // 先取后加,所以结果为4


4. int a = 5, b = 3; 执行"!a&&b++"之后,a和b的值是?
   5, 3
   !a为假,所以不必再去计算后边的式子了。


5. 下面程序中变量c的结果是?
   unsigned int a = 6;
   int b = -20;
   char c;
   (a+b>6) ? (c=1) : (c=0); 
   unsigned int类型与int类型数据运算后,自动转化为unsigned int类型。因此a+b值不是-14,
   而是一个unsigned int类型的数4294967382。所以c值为1。可以定义一个int类型变量,
   d = a + b;这样就可以了。
   这个问题主要测试对类型转换的了解。类型转换有几个原则:
   1) 为防止精度损失,必要的话,类型总是被提升为较宽的类型。
   2) 所有含有小于整型的有序类型的算术表达式在计算之前其类型都会被转换成整型。


6. 两个变量a和b,不用任何判断语句,找出较大的值。
   max = ((a+b)+abs(a-b))/2


7. 交换a、b值,不用中间变量。
   有两种方法:
   1) a=a+b; b=a-b; a=a-b; //缺点是,a、b是两个比较大的数,那a=a+b时就会越界
   2) a=a^b; b=a^b; a=a^b; //不用担心越界


8. C++程序中调用被C编译器编译后的函数,为什么要加extern "C"?
   C++支持函数重载,C不支持。函数被C++编译器编译后在库中名字与C不同。如void foo(int x, int y),
   C编译器编译后名字为_foo,C++编译后会产生像_foo_int_int之类的名字。
   C++提供了C连接交换指定符号extern "C"用来解决名字匹配问题。表明被extern "C"包含的代码是用C编译
   器编译的。


9. 声明一个常数,表明1年中有多少秒(不考虑闰年)。   
   #define SECONDS_PER_YEAR (60*60*24*365)UL


10. const用途。   
   1) 定义常量。
   2) 修饰函数参数和返回值。
   3) 防止变量被意外改动,提高程序健壮性。


11. sizeof(x),要看x具体是什么类型。
   char *a = "123"; //sizeof(a),a类型是字符指针,所以为4
   char b[] = "123"; //sizeof(b),b类型是字符数组,数组长度是"123"再加上隐含的"\0"总共是4
   int c[100]; //sizeof(c),c为整型数组,每个整型变量占4Byte,所以sizeof(c)值为400


12. class A {}; struct B {}; sizeof(A), sizeof(B)是多少?
   对于一个空类或者空结构体,它的大小是1。


13. 计算以下结构体的sizeof大小。
struct T {
static int a;
int b;
}; 
sizeof(T)为4。因为静态变量存放在全局数据区,而sizeof只计算栈区变量分配空间的大小,所以全局数据区的变量不参数计算。
   sizeof不是函数,是一个操作符,作用是返回一个对象或者类型在栈空间所占内存的大小,在程序编译时计算的,而不是运行时。
   sizeof操作符不能用于函数类型、不完全类型或位字段。不完全类型指具有未知存储大小数据的数据类型,如未知存储大小的数组
   类型、未知内容的结构或联合类型等。


14. string str[] = {"123", "456", "789"};  sizeof(str)值为12。str是字符指针数组,每个字符指针占4Byte空间大小。


15. 一个空类所占空间为1,多重继承的空类所占空间还是1。但是虚继承涉及到虚表(虚指针),所以空间为一个指针的大小,通常是4。
   class A {}; //sizeof(A) --> 1
   class B : public virtual A {}; //sizeof(B) --> 4
   class A { virtual void fun(); }; //sizeof(A) --> 4


16. 内联函数和宏的差别?
   1) 宏只是在编译前(编译预处理阶段)把宏体替换过去,不做任何参数检查,所以只是个简单的替换;
   2) inline是函数,但不单独产生代码,只是在编译中将代码嵌入到调用处,会做参数类型检查;
   3) 相比于普通函数,inline不需要中断调用,只是在编译时做代码的嵌入。
   4) 如果一个函数不断被重复调用,那么可以把这个函数写成inline。
   inline fun(int val) {return val*val;} //没写返回值的
   printf("%d", fun(8)); //调用时就是执行printf("%d", 8*8);


17. 指针和引用的区别(这个问题一般针对C++语言)?
   1) 在任何情况下都不能使用指向空值的引用,而指针可以指向空值;
   2) 正因为1)的缘故,所以使用指针时通常要判断是否为空,而引用不需要测试它的合法性;
   3) 指针可以被重新赋值,引用总是指向初始化时指定的对象,以后不能改变;
   4) 如果总是指向一个对象,并且以后不会改变指向,那么应该用引用,其他情况都用指针;
   int a; //正确
   int &b; //错误,引用不能为空,要初始化
   int &b = a; //正确,打印引用的值可以这样,cout<    int *p; //正确
   *p = 5; //错误,指针p没有指向实际的地址,这种情况赋值不知道存到哪里去,从而造成错误
   const double c; //错误,const常量必须初始化
   const double d = 10.0; //正确
   const double *k = &d; //正确


18. 类的this指针有以下特点:
   1) this只能在成员函数中使用,全局函数、静态函数都不能使用this。实际上,成员函数默认第一个参数为
      T* const this。如:
 class A { public: int func(int p){} };
 其中func的原型在编译器看来应该是:
 int func(A* const this, int p);
   2) 由此可见,this在成员函数的开始前构造,在成员的结束后清除。
      this的生命周期同任何一个函数的参数是一样的。当调用一个类的成员函数时,编译器将类的指针作为函数的
 this参数传递进去,如:
 A a;
 a.func(10);
 此处,编译器将会编译成:
 A::func(&a, 10);


19. 以下程序有什么错误?
#include
#include
#include
using namespace std;


void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char)*num);
}


int main()
{
char *str = NULL;


GetMemory(str, 100);
strcpy(str, "hello");


cout<

return 0;
}
   段错误,程序崩溃。
   p指向str,给p分配内存,只不过把p指向了另外一个地址。和str没关系,所以str一直是NULL。
错认为 GetMemory(char   *p)中的 p “就是” GetMemory(str)中的str。但p“不是”str,它只是“等于”str 。 
就象:   
int a = 100;   
int b = a;//现在b等于a   
b = 500;  //现在能认为a = 500 ?      
  显然不能认为a = 500,因为b只是等于a,但不是a!当b改变的时候,a并不会改变,b就不等于a了。    因此,虽然p已经有new的内存,但str仍然是null。

上述代码和下面这段代码是一个意思,都会出现段错误。
int main()
{
char *str = NULL;
char *p = str;
p = (char *)malloc(sizeof(char)*100);


strcpy(str, "hello");


cout<

return 0;
}
   需要用指向指针的指针,正确的程序应该是:
void GetMemory(char **p, int num)
{
*p = (char *)malloc(sizeof(char)*num);
}
int main()
{
char *str = NULL;


GetMemory(&str, 100);
strcpy(str, "hello");


cout<

return 0;
}
   或者改成用函数返回值返回申请的内存地址也可以。
   
20. 以下程序对还是错?
char* GetMemory(void)
{
char p[] = "hello";
return p;
}


int main()
{
char *str = NULL;
str = GetMemory();


cout<

return 0;
}
   可能乱码,也可能正常输出。因为函数返回的是指向“栈内存”的指针,该指针的地址不是NULL,但其原来
   的内容已经被清除,新内容不可知。   

再看以下这几个例子:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. #if 0
  5. char* GetMemory()
  6. {
  7.     char *p = (char *)malloc(100);    //p是局部变量,但malloc分配在堆上,堆生命周期不跟随函数消亡,所以运行没问题
  8.     return p;
  9. }

  10. void main( void )
  11. {
  12.     char *str = NULL;
  13.     str = GetMemory();
  14.     strcpy( str, "hello world" );
  15.     printf( "%s\n", str );
  16. }
  17. #elif 0
  18. char *GetMemory( void )
  19. {
  20.     char p[] = "hello world";    //p是局部字符数组,分配在栈区,生命周期随函数结束而结束,所以运行有问题
  21.     return p;
  22. }

  23. void main( void )
  24. {
  25.     char *str = NULL;
  26.     str = GetMemory();
  27.     printf( "%s\n", str );
  28. }
  29. #else
  30. char *returnStr()
  31. {
  32.     char *p = "hello world!"; //p是局部变量,但字符串在常量区,不会随函数结束而回收,所以运行正常。
  33.     return p;
  34. }

  35. void main(void)
  36. {
  37.     char *str;
  38.     str = returnStr();
  39.     printf("%s\n", str);
  40. }
  41. #endif



21. 写出函数指针、指向const的指针、const指针、指向const的const指针。
   void (*f)()
   const int*[推荐] / int const*
   int* const
   const int* const
   
   const* int //这种写法什么都不是,错误
   看以下示例:
int main(int argc, char *argv[])
{
int a=3;
int b;

/*定义指向const的指针(指针指向的内容不能被修改)*/ 
const int* p1; 
int const* p2; 

/*定义const指针(由于指针本身的值不能改变所以必须得初始化)*/ 
int* const p3=&a; 

/*指针本身和它指向的内容都是不能被改变的所以也得初始化*/
const int* const p4=&a;
int const* const p5=&b; 

p1=p2=&a; //正确
*p1=*p2=8; //不正确(指针指向的内容不能被修改)

*p3=5; //正确
p3=p1; //不正确(指针本身的值不能改变) 

p4=p5;//不正确 (指针本身和它指向的内容都是不能被改变) 
*p4=*p5=4; //不正确(指针本身和它指向的内容都是不能被改变) 
 
return 0; 
}   
   规律:
   1) 指向const的指针(指针指向的内容不能被修改)const关健字总是出现在*的左边;
   2) const指针(指针本身不能被修改)const关健字总是出现在*的右边;
   那不用说,*两边都加const肯定是指针本身和它指向的内容都是不能被改变的。   


22. 各种指针。
   1) int (*p)[n] //数组指针,指向数组的指针,代表它是指针,指向整个数组,亦称行指针
      p是一个指针,指向一个整型的一维数组,这个一维数组长度是n,p+1要跨过n个整型数据的长度。
 int *p[n] //指针数组,由指针组成的数组
 一般这样使用:
 int a[m][n];
 int (*p)[n] = a;

   2) int* (*p)[n] 或者 int* ((*p)[n])
      p是一个指针,指向一个int*类型的一维数组,数组元素是int*类型。
   
   3) int (**p)[n] 或者 int (*(*p))[n]
      一般这样使用:
 int a[m][n];
 int (*p)[n] = a;
      int (**k)[n] = &p;
      p是一个二级指针,指向一个一维数组的指针。


   4) int (*p[n])()
      p是一个有n个元素的数组,每一个元素都是一个指向函数的指针,指向的函数类型是没有参数,返回值是int。


   5) int (*fun)(int)
      函数指针,指向的函数参数为int类型,返回值也是int类型。

   6) int *fun(int)
      指针函数,返回值为int*的函数。
 
   7) int (*(*F)(int,int))(int)
      F是一个函数指针,指向的函数类型是有两个int参数并且返回值是一个函数指针,返回的函数指针指向有一个
 int参数且返回值是int的函数。


23. 空指针和迷途指针区别?
   1) delete一个指针,只是释放内存,指针本身依然存在。这时它就是一个迷途指针。
      p=0; 这样可以把迷途指针变为空指针。
   2) 通常,delete一个指针后如果不置为空,那么会变为一个迷途指针,这时再delete一次,程序会变得非常不稳定,
      任何情况都有可能发生,因为迷途指针指向内容不确定。
 而你删除的是一个空指针,则什么事都不会发生,没有什么隐患。
   3) 使用迷途指针或空指针都是非法的,都有可能造成程序崩溃。但是空指针造成的崩溃相比是一种可预料的,比较
      容易定位。


24. C++中有了malloc/free,为什么还需要new/delete?
   1) malloc是标准库函数,new是运算符。
   2) 对象的构造和析构函数new/delete时会自动调用到,而malloc/free不具体这样的功能。


25. 求N!。
#include
using namespace std;


int find(int n)
{
if (1 == n)
return 1;
else
return find(n-1)*n;
}


int main(int argc, char *argv[])
{
int n, sum = 0;
cin >> n;
sum = find(n);
cout<

return 0;
}


26. 斐波那契数列:1、1、2、3、5、8、13、21、…… 
  如果设F(n)为该数列的第n项(n∈N+).那么这句话可以写成如下形式:
  F(0) = 0,F(1)=F(2)=1,F(n)=F(n-1)+F(n-2) (n≥3)
  显然这是一个线性递推数列. 用代码实现通项公式f(n)。
#include
using namespace std;


int f(int n)
{
if (1 == n || 2 == n)
return 1;
else
return f(n-1)+f(n-2);
}


int main(int argc, char *argv[])
{
int n, sum = 0;
cin >> n;
sum = f(n);
cout<

return 0;
}






  
上一篇:要掌握的常用数据结构
下一篇:malloc是怎么实现的