C/C++知识点清单01

时间:2024-12-06 23:34:07

第一章  C/C++程序基础

一、一般赋值语句:

考察一般赋值语句的概念和方法。

1.程序:

 #include<stdio.h>

 int main(void)
{
int x=,y,z; x*=(y=z=); printf("x=%d\n",x); z=;
x=(y=z); printf("x=%d\n",x);
x=(y==z); printf("x=%d\n",x);
x=(y&z); printf("x=%d\n",x);
x=(y&&z); printf("x=%d\n",x); y=;
x=(y|z); printf("x=%d\n",x);
x=(y||z); printf("x=%d\n",x); x=(y==z)? :(y<z)? :;
printf("x=%d\n",x); return ;
}

2.答案:

   x=
  x=
  x=
  x=
  x=
  x=
  x=
  x=
  x=

3.分析:

  代码行.12中的“&”表示位与计算,即将y和z转换为二进制数字10和10,进行位与计算。1&1=1,其余都是0,故结果为二进制10。所以答案为2,x=2。

  代码行.16中的“|”表示位或计算,方法同上。0|0=0,其余都是1,故结果为二进制110。所以答案为6,x=6。

4.小结:

  一般赋值语句,记住符号的优先级即可。另外记住一些特殊的运算符,如位计算符、三目运算符。

  C/C++知识点清单01

  C/C++知识点清单01

  C/C++知识点清单01

  (图片来自网络资源)

二、C++域操作符:

1.程序:

 #include<stdio.h>

 int value=;

 void printvalue()
{
printf("value=%d\n",value);
}; int main()
{
int value=; value=;
printf("value=%d\n",value); ::value=;
printvalue(); return ;
}

2.答案:

(C++中输出)

value=        //局部变量value
value= //全局变量value

PS:C语言下无法编译通过。

3.分析:

  这段程序包含着两个value变量。其中一个是在main函数前就声明的全局变量,另一个是在main函数内部声明的局部变量。这两者作用域是不同的(局部变量只可以作用鱼定义它的函数内部)。

  在main函数中代码行.15中输出的是局部变量value的值(value=1)。这是因为在main函数中的局部变量value引用优先。在C++中可以通过域操作符“::”来直接操作全局变量(代码行.17改变了全局变量value的值)。在之后的代码行.18中调用printvalue函数输出全局变量value的值(value=2)。

三、i++与++i哪个效率更高:

  其实,单纯考虑前缀自增运算和后缀自增运算是没有意义的。首先考虑内建数据类型的情况:如果自增运算表达式的结果没有被使用,而只是用于增加一员操作数,那么两者没有任何区别。主要需要查看自定义数据类型的情况。

1.程序:

 #include<stdio.h>

 int main()
{
int i=;
int x=; i++;
++i;
x=i++;
x=++i; return ;
}

  经过VC++6.0编译,得到如下汇编代码:

;Line 5
mov DWORD PTR _i$[ebp],
Line
mov DWORD PTR _x$[ebp],
Line
mov eax,DWORD PTR _i$[ebp]
add eax,
mov DWORD PTR _i$[ebp],eax
Line
mov ecx,DWORD PTR _i$[ebp]
add ecx,
mov DWORD PTR _i$[ebp],ecx
Line
mov edx,DWORD PTR _i$[ebp]
mov DWORD PTR _x$[ebp],edx
mov eax,DWORD PTR _i$[ebp]
add eax,
mov DWORD PTR _i$[ebp],eax
Line
mov ecx,DWORD PTR _i$[ebp]
add ecx,
mov DWORD PTR _i$[ebp],ecx
mov edx,DWORD PTR _i$[ebp]
mov DWORD PTR _x$[ebp],edx

2.答案:

  内建数据类型,效率没有区别。

  自定义数据类型,++i效率更高。

3.分析:

  代码行.8-9生成的汇编代码几乎完全一致。

  代码行.10-11生成的汇编代码只是在加1的先后顺序上有所区别,效率也完全一致。

  由此说明,考虑内建数据类型时,它们的效率差别不大。

  那么再考虑自定义数据类型(主要指类)的情况,由于前缀式(++i)可以返回对象的引用,而后缀式(i++)必须返回对象的值,从而导致在大对象的时候产生较大的复制开销,引起效率降低。故自定义数据类型时,前缀式递增/递减效率更高。

4.小结:

  表示分析中的最后一段,估计很多如我一般的萌新都感觉不是很懂。所以经过百度和查阅后,给大家一个翻译后的解释。

  (以下代码来自博客:为什么(i++)不能做左值,而(++i)可以
    // 前缀形式:
  int& int::operator++()
//这里返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用
  {
//函数本身无参,意味着是在自身空间内增加1的
   *this += ; // 增加
   return *this; // 取回值
  }   //后缀形式:
  const int int::operator++(int)
//函数返回值是一个非左值型的,与前缀形式的差别所在。
  {
//函数带参,说明有另外的空间开辟
   int oldValue = *this; // 取回值
   ++(*this); // 增加
   return oldValue; // 返回被取回的值
  }

  故++i与i++两者的具体实现函数的不同造成在引用时,两者所占据的资源不同。

四、选择编程风格良好的条件选择语句:

1.程序:

 A.布尔型:假设布尔变量名字为flag,它与零值比较的标准if语句如下:

  第一种:

     if(flag==TRUE)
if(flag==FALSE)

  第二种:

     if(flag)
if(!flag)

 B.整型:假设整型变量的名字为value,它与零值比较的标准if语句如下:

  第一种:

     if(value==)
if(value!=)

  第二种:

     if(value)
if(!value)

 C.浮点型:假设浮点变量的名字为x,它与零值比较的标准if语句如下:

  第一种:

     if(x==0.0)
if(x!0.0)

  第二种:

     if((x>=-EPSINON)&&(X<=EPSINON))
if((X<-EPSINON)||(X>EPSINON))

PS:其中EPSINON为允许的误差(精度)。

 D.指针型:指着变量p与0的比较如下:

  第一种:

     if(p==NULL)
if(p!=NULL)

  第二种:

     if(p==)
if(p!=)

2.答案:

 A:后者风格较为良好。

 B:前者风格较为良好。

 C:后者风格较为良好。

 D:前者风格较为良好。

3.分析:

 A:根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值并没有统一的标准。因此,不可将布尔变量直接与TURE、FALSE进行比较。

 B:后者风格容易令人误解value为布尔变量。所以应该将整型变量用“==”与“!=”直接与0比较。

 C:浮点型变量都有精度限制。一定要极力避免将浮点变量通过“==”或“!=”与数字进行比较,应该设法转化为精度与“>=”、“<=”间的比较。(论证在C教程上)

 D:指针变量的零值为“空”(记为NULL)。尽管NULL的值与0相同,但是两者意义不同。用p与NULL显式比较,强调p是指针。

五、有符号变量与无符号变量的值的转换:

(有符号变量和无符号变量的区别:http://blog.****.net/gogokongyin/article/details/39758289)

1.程序:

 #include<stdio.h>

 char getChar(int x,int y)
{
char c;
unsigned int a=x; (a+y>)? (c=):(c=);
return c;
} int main(void)
{
char c1=getChar(,);
char c2=getChar(,);
char c3=getChar(,-);
char c4=getChar(,-); printf("c1=%d\n",c1);
printf("c2=%d\n",c2);
printf("c3=%d\n",c3);
printf("c4=%d\n",c4); return ;
}

2.答案:

    c1=
c2=
c3=
c4=

3.分析:

  在getChar函数中,有两个整型的输入参数x和y。在函数内,把参数x的值转换为无符号(unsigned)整型后再与y相加。之后再与10比较。

  注意:在表达式中存在有符号类型和无符号类型时,所有的操作数都自动隐式转换成无符号类型。

  代码行.14中,传入的参数分别为7和4,两个数相加后为11,因此c1返回1。

  代码行.15中,传入的参数分别为7和3,两个数相加后为10,因此c2返回2。

  代码行.16中,传入的参数分别为7和-7,在signed下,-7为7的原码00000000 00000111的反码11111111 11111000加1,得到其补码11111111 11111001。在signed下,整数通过16位开头第一位区别正负,0为整数,1为负数。余下的15位才是真正的数字段。但是unsigned下,是没有负数的,也就是说整整16位都是数字段。所以signed下的-7,在unsigned下转化为65529。那么7和65529相加后为65536,这个值得大小正好溢出(unsigned int数据范围为0-65535共计65536个数字)(实际得到的结果为0)。因此c3返回2。

PS:我只按照16位解释,将位数增加到32位也是没有区别的。

  代码行.17中,传入的参数分别为7和-8,实际转化方式如上,最终相加得到的值为65535。因此c4返回1。

4.小结:

  其实这个程序难点在于两处:一方面,要知道有符号、无符号的概念与区别;另一方面,知道原码、反码、补码的计算。

  当然,你十分熟悉这个过程,一眼就看出来也行。通过7+(-7)=0,知道-7为是否溢出的负数上限,看出负数中[-65535,-8]满足条件,[-7,-1]会导致溢出(正数大家都可以一眼看出)。

六、不使用任何中间变量如何将a、b的值进行转换:

  表示这个问题我在刚学编程是就想过,因为我们老师说转换两者值必须中间变量,然后我就想出通过加、减(下面程序第二个方法)写出代码,给老师看去了。

  表示我“打”老师脸最开心的几次。淡定ing。

1.程序:

 #include<stdio.h>

 void swap1(int& a,int& b)
{
int temp=a; //使用局部变量temp完成交换
a=b;
b=temp;
}; void swap2(int& a, int& b)
{
a=a+b; //使用加减运算完成交换
b=a-b;
a=a-b;
}; void swap3(int& a,int& b)
{
a^=b; //使用异或运算完成交换
b^=a;
a^=b;
}; int main(void)
{
int a1=,b1=;
int a2=,b2=;
int a3=,b3=;
int a=,b=; swap1(a1,b1); //测试使用临时变量进行交换的版本
swap2(a2,b2); //测试使用加减运算进行交换的版本
swap3(a3,b3); //测试使用异或运算进行交换的版本 printf("after swap...\n");
printf("a1=%d,b1=%d\n",a1,b1);
printf("a2=%d,b2=%d\n",a2,b2);
printf("a3=%d,b3=%d\n",a3,b3); swap2(a,b);
printf("a=%d,b=%d\n",a,b); return ;
}

2.答案:

    after swap...
a1=,b1=;
a2=,b2=;
a3=,b3=;
a1=,b1=

3.分析:

  首先,swap1函数就不谈了。通过一个中间变量temp来达到交换目的。其次,swap2函数就是通过a,b的和与差来进行转化,也就是需要对数学有一定的基础和灵性。

  之后,就是一个小重点(没办法,学校C课程对位运算提到原码,补码,反码),那就是通过位运算中的异或运算来实现数值转换。

4.小结:

  其实从某种角度上来说第三种方法时前两种方法的综合。一方面,这个方法存在中间变量,那就是代码行.19中的最终结果a。另一方面,这个方法将这个中间变量保存在了两个变量中的变量a。之所以没说这个方法和第二种一致,是因为它只用了两个变量中的一个变量存储中间变量就是实现了最终目的。所以,我很喜欢它。

七、标准头文件的结构:

考察标准头文件中一些通用结构的理解。

1.程序:

 #ifndef _INCvxWorksh
#define _INCvxWorksh
#ifdef _cplusplus
extern "C" {
#endif
/*...*/
#ifdef _cplusplus
}
#endif
#endif /* _INCvxWorksh */

2.答案:

  代码行.1,2,10中代码的作用是防止头文件被重复引用。

  代码行.3中代码的作用是表示当前使用的是C++编译器。

  代码行.4-8中的extern"C"是C++编译器提供的与C连接交换指定的符号,用来解决名字匹配问题。

3.分析:

  代码行.1,2,10中代码是为了防止头文件的重复引用。因为常常一个CPP文件有多个头文件,而头文件之间又是可以相互引用的,这就可能造成头文件的重复引用,当头文件被重复引用时,编译器就会报错,显示头文件被重复定义。

  那么避免重复引用头文件有两种方法:

   1.使用 ifndef/define/endif 结构产生预处理块

    优点:由于这个语句是语言相关的,可移植性好。

    缺点:由于只有打开了某个头文件,编译器才能根据保护宏确定是否引用过了。所以效率相对较低。但是这种方法是通过保护宏名来确定某个头文件是否被引用过,那么宏名重复,就出现需要的头文件明明就在那里,编译器却说找不到这个头文件(因为那个头文件因为保护宏名重复而被屏蔽了)。

   2.使用 #pragma once

    优点:由编译器提供保证,同一个文件不会被包含两次(这里的包含指的是物理地址,即文件的路径地址)。所以效率较高。

    缺点:由于头文件的屏蔽是编译器通过头文件物理地址的记录来实现的。所以如果某一个头文件有多个复制的副本,依旧会造成头文件重复引用的问题。

       另外这个方法只能在微软开发工具上实现。很多编译器没有,或者有这个方法的编译器打算移除。

    (PS:上述部分资料引用于http://blog.****.net/zhl30041839/article/details/37728237)

  代码行.3表明了当前使用的编译器为C++编译器。如果需要表明为C编译器,则语句可以表示为“#indef _STDC_”。

  代码行.4-8中 extern "C"包含着双重意义。这句话主要由两个部分“extern”与“C”组成。

    前者是C/C++的一个重要关键字,用于表明其修饰的变量和函数作用范围。extern表明其声明的变量和函数可以在本模块或其他模块中使用。通常,在模块的头文件中对本模块提供给其他模块引用的函数和全局变量以关键字extern声明。在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

    (PS:上述部分资料引用于http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html)

    后者表示其修饰的变量和函数是按照C语言的方式编译和连接的。由于C++支持函数重载,而过程式语言C则不支持,故C++和C语言的函数编译是存在不同的。

    例如:“void foo(int x,int y);”与“void foo(int x,float y);”在C++语言也许会被编译为“_foo_int_int”与“_foo_int_float”(C++通过这种机制来实现函数重载),但在C语言编译中都是“_foo”。这就会造成在C中连接C++编译的函数符号时,就会因此找不到匹配的符号而发生连接错误。如果添加了extern“C”声明后,模块编译会生成foo的目标代码时,就不会对其名字进行特殊处理,而采用C语言的方式处理(也就是不会添加参数表信息)。

八、#include<head.h>与#include"head.h"的区别:

1.答案:

  尖括号<>表明其中的文件head.h是一个工程或者标准头文件。所以编译器会在查找过程中优先检查预定义的目录。当然,我们可以通过设置搜索路径环境变量或命令行选项来修改这些目录。

  引号""表明其中的文件head.h是有用户提供的头文件。所以编译器在查找过程中会优先从当前文件目录(或文件名指定的其他目录)中寻找该文件,然后再在标准位置寻找文件。

九、C++中main函数执行完后还需要执行的语句:

1.程序:

 #include<stdlib.h>    //使用atexit()函数必须包含头文件stdlib.h
#include<stdio.h> void fn1(void);
void fn2(void); int main(void)
{
atexit(fn1); //使用atexit注册fn1()函数
atexit(fn2); //使用atexit注册fn2()函数
printf("main exit...\n");
return ;
} void fn1()
{
printf("calling fn1()...\n"); //fn1()函数打印内容
} void fn2()
{
printf("calling fn2()...\n"); //fn2()函数打印内容
}

运行结果:

    main exit...
calling fn2()...
calling fn1()...

2.答案:

  可以通过atexit()函数(at-exit)来注册程序正常终止时要被调用的函数,并且在main()函数结束时,调用这些函数顺序和注册它们的顺序相反。

3.分析:

  我们常常需要在程序结束时解决一些类似资源释放的操作,但是程序退出的方法很多(有兴趣的可以看看WIN32编程中提及的),所以我们需要一个一种与程序退出方式无关的方式,来进行那些在程序退出时进行的必要操作。

  atexit()函数就是这样一个满足条件的函数,它通过注册程序正常终止时需要被调用的函数。其函数原型为:

         int atexit (void (*)(void));

  在一个程序中最多可以通过atexit()函数注册32个必要处理函数。不过这些必要处理函数的调用顺序和它们的注册顺序恰好相反。

  (PS:有时也有一些有关进程关闭的语句会令这个函数无法使用,具体可以参照百度百科-atexit)