1.
在用#define 定义时 , 斜杠("\")是用来续行的,"#"用来把参数转换成字符串,是给参数加上双引号。"##"则用来连接前后两个参数,把它们变成一个字符串,"#@"是给参数加上单引号。
#define Cat(x,y) x##y /* Cat(1,2) == "12"*/
#define ToChar(a) #@a /* ToChar(1) == '1' */
#define ToString(x) #x /* ToString(1) == "1"*/
同时需要记住,在C语言中,以空格隔开的连续的多字符串会自动连接。
char a[]="string1""string2";
char a[]="string1" "string2";
此两句都是合法的,等价于:char a[]="string1string2";
此语法经常与前面的宏定义一起使用。
#define p(x) printf(#x" is here\n")
p(hehe); /*hehe 没有预定义*/
即printf("hehe"" is here\n")等价于 printf("hehe is here\n")
2. 引用与指针:
都是用来间接访问另一个值,区别有两个:
(1) 引用总是指向某个对象,定义引用时没有初始化是错误的。
(2) 赋值行为差异:引用赋值修改的是该引用所关联的对象值,不是使引用与另一个对象关联。
引用已经初始化就始终指向一个特定对象。
int val=12, val2=3;
int *pi=&val, *pi2=&val2;
pi = pi2;
赋值修改pi指针的值,改为指向val2;
int &ri=val, &ri2=val2;
ri =ri2;
ri仍然指向ival,同时用ival2的值赋值给ri指向的val
3. C++定义了const成员函数作为函数重载,在const对象调用的时候,选择const函数。
4.auto修饰符仅在语句块内部使用,初始化可为任何表达式,其特点是当执行流程进入该语句块的时候执行初始化操作,没有默认值。
使用register修饰符修饰变量,将暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。
static静态变量声明符。在声明它的程序块,子程序块或函数内部有效,值保持,在整个程序期间分配存储器空间,编译器默认值0。是C/C++中很常用的修饰符,它被用来控制变量的存储方式和可见性。static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。
extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行链接指定。
5.结构是一种用关键字struct声明的自定义数据类型。与类相似,也可以包含构造函数,常数,字段,方法,属性,索引器,运算符和嵌套类型等,不过,结构是值类型。但是,C++的结构和类是有区别的:
(1) 结构的构造函数和类的构造函数不同。
a.结构不能包含显式的无参数构造函数。结构成员讲自动初始化为它们的默认值。
b.结构不能包含以下形式的初始值设定类:base(argument-list);
(2) 对于结构中的实例字段成员,不能在声明时赋值初始化。
(3) 声明了结构类型后,可以使用new运算符创建构造对象,也可以不使用new,如果不使用new,那么在初始化所有字段之前,字段将保持未赋值状态且对象不可用。
(4) 结构不支持继承,即一个结构不能从另一个结构或类继承,而且不能作为一个类的基类。但是,结构从基类OBJECT继承。结构也可以实现接口。
(1) struct不允许显示声明其无参数构造函数,这不同于class
(2) struct不允许声明时,初始化其数据成员值
(3) struct作为参数传递时,可考虑使用ref,以优化性能:因为是值类型(但要注意其值的改变)
(4) struct无继承,但其本身继承自System.ValueType ----> System.Object
(5) struct可看作是缩小的class,适宜小数据成员时使用
(1) struct 是值类型,class是对象类型
(2) struct 不能被继承,class可以被继承
(3) struct 默认的访问权限是public,而class默认的访问权限是private.
(4) struct不能由程序员申明构造函数,即使是默认(不带参数)的构造函数,同样也不能有析构的处理部分。这是因为Struct的构造函数是由编译器自动生成的。并且Struct的用途是那些描述轻量级的对象,例如Line,Point等,并且效率比较高。
(5) struct的new和class的new是不同的。struct的new就是执行一下构造函数创建一个新实例再对所有的字段进行Copy。而class则是在堆上分配一块内存然后再执行构造函数,struct的内存并不是在new的时候分配的,而是在定义的时候分配
6.下列关于虚函数的说法正确的是(CD)
A、在构造函数中调用类自己的虚函数,虚函数的动态绑定机制还会生效。
B、在析构函数中调用类自己的虚函数,虚函数的动态绑定机制还会生效。
C、静态函数不可以是虚函数
因为静态成员函数没有this,也就没有存放vptr的地方,同时其函数的指针存放也不同于一般的成员函数,其无法成为一个对象的虚函数的指针以实现由此带来的动态机制。静态是编译时期就必须确定的,虚函数是运行时期确定的。
D、虚函数可以声明为inline
inline函数和virtual函数有着本质的区别,inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制。
因此,内联函数是个静态行为,而虚函数是个动态行为,他们之间是有矛盾的。
函数的inline属性是在编译时确定的, 然而,virtual的性质则是在运行时确定的,这两个不能同时存在,只能有一个选择,文件中声明inline关键字只是对编译器的建议,编译器是否采纳是编译器的事情。
我并不否认虚函数也同样可以用inline来修饰,但你必须使用对象来调用,因为对象是没有所谓多态的,多态只面向行为或者方法,但是C++编译器,无法保证一个内联的虚函数只会被对象调用,所以一般来说,编译器将会忽略掉所有的虚函数的内联属性。
相关知识点:什么函数不能声明为虚函数?
一个类中将所有的成员函数都尽可能地设置为虚函数总是有益的。
设置虚函数须注意:
(1) 只有类的成员函数才能说明为虚函数;
(2) 静态成员函数不能是虚函数;
(3) 内联函数不能为虚函数;
(4) 构造函数不能是虚函数;
(5) 析构函数可以是虚函数,而且通常声明为虚函数。
7. 函数重载,不能根据返回类型的不同进行;形参与const形参的等价性仅适用于非引用形参。有const引用形参的函数与非const引用形参的函数是不同的。
8.
(1) 构造函数本身不能是虚拟函数;并且虚机制在构造函数中不起作用(在构造函数中的虚拟函数只会调用它的本地版本)。
想一想,在基类构造函数中使用虚机制,则可能会调用到子类,此时子类(应为父类)尚未生成,有何后果!?。
(2) 析构函数本身常常要求是虚拟函数;但虚机制在析构函数中不起作用。
若类中使用了虚拟函数,析构函数一定要是虚拟函数,比如使用虚拟机制调用delete,没有虚拟的析构函数,怎能保证delete的是你希望delete的对象。
虚机制也不能在析构函数中生效,因为可能会引起调用已经被delete掉的类的虚拟函数的问题。
9. 杂集
(1) char buf[4]="hell"; //长度不足,编译不出问题,但是运行的时候回出现不可预知的错误。
(2) strcpy(dest, src);//dest长度不足,运行出错,错误不可预知,要求dest与src地址空间不能有重叠。
(3) unsigned char c=(unsigned char)-1; int i = (int)c; //i=255
signed char c=(unsigned char)-1; int i=(int)c; //i=-1
char 默认为 signed char,转换的时候需要注意第一位是是否为0
char c=233; int i=c; // %d -23, %x ffffffe9 %u 4294967273
unsigned char c=233; int i=c; // %d 233, %x e9 %u 23
char c=127; int i=c; // i=0x7f; 即127,没有负数
(4) int i=0xff000000; i>>=1; // 0xff800000
unsigned int i=0xff000000; i>>=1; //0x7f800000
(5) char *str="\x56\x78\x12\x34"
\x为16进制转义字符,看似字符串为56781234,实际是转义为对应的十六进制数,长度为4,不能超过两位,如\x512是不符合的,此时在本人电脑等价\x12,如果是\x5,等价\x05。
int i= *(int*)str; //i=0x34127856,因为x86一般是小端字节序,即低位数字(56)放在低地址(56),字符串的存放是地址递增,因此56在最高位,因此是数字的最低位56,得到结果。注意数字最低位是从右边开始的个位数。
(6) std::string str1("hello"), str2("world");
memcpy(&str2, &str1, sizeof(str1)); //出现错误,类不能简单复制,需要类方法
10.Base类中声明了foo1()函数,Derived类中声明了foo2()函数,Derived继承Base。
void (Base::*pfn)=&Derived::foo2(); //错误
void (Derived::*pfn)=&Base::foo1(); //正确
11. 对于带虚函数的类,运行时执行RTTI(运行时类型识别);对于其他类,编译时计算RTTI操作符。
程序使用基类的指针或引用指向继承类,执行时候需要动态转换,判断是执行基类部分或其它,
dynamic_cast操作符将基类指针或引用转换为同一继承层次中其它类型的引用或指针,使用的指针必须有效(0或指向一个对象)。如果绑定到指针或引用的对象不是目标类型的对象,则失败,指针类型失败返回值为0 ,引用类型失败抛出bad_cast类型的异常。
网易笔试题如下:
class Base
{
//virtual void dummy(){}
void dummy(){}
};
class Derived : public Base
{
};
Derived *d1 = NULL;
Derived *d2 = NULL;
int main(int argc, char**argv)
{
Base *b1 = new Derived();
Base *b2 = new Base();
printf("b1=0x%x\n", b1);
printf("b2=0x%x\n", b2);
d1 = dynamic_cast<Derived*>(b1);
d2 = dynamic_cast<Derived*>(b2);
printf("d1=0x%x\n", d1);
printf("d2=0x%x\n", d2);
return 0;
}
d1应当与b1相同,d2类型转换失败,为0。如果将虚函数去掉,静态编译时决定,无法调用dynamic_cast。
12.宏定义的常用结构:#define A do { A } while(0),原因是:http://blog.csdn.net/stoneliul/article/details/7870025
(1) 空的宏定义避免warning:
#define foo() do{}while(0)
(2) 存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。
(3) 如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:
#define foo(x) \
action1(); \
action2();
在以下情况下:
if(NULL == pPointer)
foo();
就会出现action2必然被执行的情况,而这显然不是程序设计的目的。
(4) 以上的第3种情况用单独的{}也可以实现,但是为什么一定要一个do{}while(0)呢,看以下代码:
#define switch(x,y) {int tmp; tmp=x;x=y;y=tmp;}
if(x>y)
switch(x,y);
else //error, parse error before else
otheraction();
在把宏引入代码中,会多出一个分号,从而会报错。
使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。