C++语言知识点
1. 一般说来,volatile用在如下的几个地方:
(1)、中断服务程序中修改的供其它程序检测的变量需要加volatile;
(2)、多任务环境下各任务间共享的标志应该加volatile;
(3)、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
2. 判断char是有符号还是无符号
C语言中的char是有符号还是无符号有时候这个问题和平台或者编译器有关,gcc和 Ms cl 都默认是signed。其实C标准并没有规定char 应该是unsigned还是signed. 事实上标准定义了三种类型:char, signed char 和unsigned char.
可以通过检查CHAR_MIN 的值就知道默认的是有符号还是无符号。
代码如下:
#include <stdio.h>
#include <limits.h>
int main()
{
printf("%d\n",CHAR_MIN);
return 0;
}
若等于0 ,则说明是无符号的,否则,它就是等于SCHAR_MIN (一般是-128)
4.C++中的CV限定
不能有CV限定符,在C++中CV限定符指const和volatile。
在 C++ 中,普通函数(非类的成员函数)不能有 CV 限定,即const和volatile限定。即非类的成员行数,用const进行修饰:
int test() const //这是不对的,普通函数(非成员函数不能有CV约束)
{
//实现
}
在 C++ 中,静态成员函数(static成员函数)不能有 CV 限定,即const和volatile限定。即类的静态成员函数,也不能有 CV 约束:
class Test
{
public:
static int test_fun() const; //这个是不允许的。
}
5.C++中为什么用模板类。
答:(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
6.C语言中的输入输出
Q: 既然fgetc是接收输入的字符,返回值用char或者unsigned char不就行了,为什么用int呢?
A: 这个主要是因为文件结束或者读写文件出错的标志被规定成EOF,也就是-1导致的。unsigned char根本取不到-1这个值,而如果用char做返回值的话,它无法分辨0xFF字符和EOF,因为这两个数值都被char认为是-1,所以它也不能作为返回值。
11. 兰亭集序有361个中文字符(含标点),请问存储在文本文件中的时候,文件大小可能是多大?
(A) 722字节 UTF-16 (这个不对,因为UTF-16有Big Endian和Little Endian两种,必须要加BOM)(这个我答错了)
(B) 724字节 UTF-16 (这个是对的,UTF-16两字节表示一个汉字,外加一个BOM两字节)
(C) 1083字节 UTF-8 (这个是对的,UTF-8通常三字节一个汉字,选用不加BOM的方式)
(D) 1086字节 UTF-8 (这个是对的,UTF-8通常三字节一个汉字,选用加BOM的方式)
(E) None of the above
7. 有哪几种情况只能用intialization list 而不能用assignment?
答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
8. C++是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
9. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在main 函数之前执行。
10. 字符串字面常量类型
字符串字面值在C中的类型是char*,而在C++的类型是const char*,并且字符串常量保存在只读的数据段,而不是像全局变量那样保存在普通数据段(静态存储区)
注意: static char d[] = { "Hello"[0] } ;//编译出错,初始值不是常量。
当一个字符串字面量作为初始化器初始化字符数组时,由于不进行数组到指针的转换,其形式实际上相当于用每一个字符元素(字符常量)初始化该数组,因此可以用于初始化静态字符数组。上面的例子其实等价于:
static char c[ ] = { 'h', 'e', 'l', 'l', 'o' };
11. typedef定义多个类型
typedef 中可以一次定义多个类型,如:
typedef int a,b,c;
这样的结果就是a,b和c都是代表int
12. C++中不能在表达式声明类型
在C++中,不能在表达式中声明类型,如在类型转换表达式和sizeof表达式中。另外,一些sizeof表达式的值因为作用域改变和字符字面值类型而在C语言和C++中有所不同。
13. C++中的switch语句
C++编译器规定:如果要在switch语句中定义和初始化变量,必须把他们放在一个语句块内。
在 C++中,switch-case中的case实质上只是一个标签(label),就像goto的标签一样。case中的代码并没有构成一个局部作用域, 虽然它的缩进给人一种错觉,好像它是一个作用域。也就是说,所有在case里面定义的变量作用域都是switch{...},在后面其他case中依然可 以访问到这个变量。而switch本质上相当于goto.
14. C++中的初始化与赋值
C++中初始化和赋值是不同的,类的初始化和赋值过程可以很好的显示出来。类中的复制初始化和赋值初始化
C与C++的区别:
C++是C的超集,C++98是以C89为子集的。所以C++自然比C增加了很多的内容。所以,如果要比较C++与C的区别,C++相比C增加的部分自然就不用说了,关键在于C++和C的交集部分两者的不同的处理方式,还有就是C中有而C++中却没有的东西,而这些其实更多的是存在于细节中。
在C++中,模板是泛型编程的基础。实际上,C++中"泛型编程"的定义就是"使用模板"
template形式的构造函数并不会隐藏由编译器隐式生成的default构造函数
sort算法的参数是随机迭代器,list的迭代器不属于随机迭代器
多态的实现是通过指针和引用;而对象的转换只会造成对象切割,不能实现多态。
15. 明智的使用私有继承
私有继承的作用:
1.不会将派生类对象转换为基类对象。
2.从私有基类对象继承而来的成员都成为了派生类的私有成员。
意义:私有继承是"用。。。来实现"作用,其目的就是派生类要复用基类已经实现的代码, 私有继承就是"实现"继承。
16. C++中static的内部机制及其使用特点:
内部机制
静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
这样, 它的空间分配有三个可能的地方 ,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。
静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。 类声明只声明一个类的"尺寸和规格",并不进行实际的内存分配,所以在类声明中写成定义是错误的。 它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
使用特点:
(1) 类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
(2)不能将静态成员函数定义为虚函数。
(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个"nonmember函数指针"。
(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X Window系统结合,同时也成功的应用于线程函数身上。
(5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。
(6)静态数据成员在<定义或说明>时前面加关键字static。
(7)静态数据成员是静态存储的,所以必须对它进行初始化。
(8)静态成员初始化与一般数据成员初始化不同:
初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用作用域运算符来标明它所属类;
所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>
(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们又重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。
17. string的写时拷贝CopyOnWrite问题
关于在"写时拷贝"发生的情况下直接操作string中内容出现的问题
对一个string对象的修改引起了另一个string对象的同步修改。是因为有函数对string对象的buf内容直接进行了操作,破坏了"写时拷贝"的规则。下面这个例子说明了问题是如何产生的,已经如何避免:
{
string str1 = "abcd";
string str2 = str1;
char *p1 = const_cast<char*>(str1.c_str());
p1[0] = 'o';
//这里str1和str2同时被修改了
printf("%s %s\n", str1.c_str(), str2.c_str());
string str3 = "abcd";
string str4 = str3;
char *p2 = &(str3[0]);
p2[0] = 'o';
//这里只有str3被修改,str4不变
printf("%s %s\n", str3.c_str(), str4.c_str());
return 0;
}
上面的程序的运行结果如下:
obcd obcd
obcd abcd
str1和str2被同时改变这个比较好理解。因为str2的初值为str1,根据"写时拷贝"原则,在str2的值或者str1的值不发生改变的时候,2个对象实际上指向了同一块内存地址。
然而我们使用c_str方法取出了str1的buf首地址,且强制转换成了char*类型,这个强制转换的操作就是破坏C++标准的罪魁祸首了。它使得编译器无法获知通过指针p1对buf内容的修改操作而自动进行拷贝操作,结果就是str1和str2对象都被修改了。
而str3和str4为什么没有被同时修改呢。 原因是指针p2的取值方式,它使用了[]操作符。C++标准认为,当你通过迭代器或[]获取到string的内部地址的时候,string并不知道你将是要读还是要写。这是它无法确定,为此,当你获取到内部引用后,为了避免不能捕获你的写操作,它在此时废止了"写时拷贝"技术!
实际上,将const char*强制转换为char*类型是问题的根本原因,他破坏了C++标准,我们应该尽量避免这么做。但现实是我们已经有很多接口这样实现了,这是一种典型的像C一样使用C++的行为。所以如果你遇到这种情况,记得使用[]操作符来获取string的第一个字符的地址,而不要使用c_str方法。