前言
收集一些关于c/c++的常见问题,以备不时之需。
1.staic关键字在C和C++当中的作用以及它们之间的区别。
(1)在c当中的作用
隐藏(对变量和函数)
在C语言中,static修饰的函数和变量具有只对本文件可见的作用,因此它具有对其他源文件隐藏的功能,利用这一特性可以在不同源文件定义同名函数和同名变量,从而不会引起命名冲突。
保持变量持久,默认初始值为0(对变量)
也就是说,用static修饰的变量,生命周期是全局的,会一直存在到程序运行结束,但它的定义域还是跟普通变量一样。用static修饰的变量会存储在静态存储区,这里的变量会在程序刚开始运行的时候就进行初始化,也是唯一的一次初始化。同时静态存储区的字节默认初始值都为0x00,全局变量也存储在这里。
(2)在c++当中的作用
当然static在C中的作用在C++中同样适用。
类的静态成员函数是属于整个类的而非某个对象,并且它没有this指针。由此引申出,它不能访问非静态数据和非静态成员函数,以及不能被声明为virtual、const、volatile。这几点需要对C++的内存模型有一定的理解才能明白。
类的静态数据成员同样是属于类的,对象都可以进行访问。初始化需要在类外进行。初始化格式:<数据类型><类名>::<静态数据成员名>=<值>。
一个注意点:只有静态常量整型变量才可以在类里面初始化,其他都不行。
在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate = 2.25;static关键字只能用于类定义体内部的声明中,定义时不能标示为static
在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。
2.C++中的NULL和nullptr之间的区别是什么?
(1)C中的NULL
在C语言中我们将NULL用作空指针之用,NULL在C语言中的定义如下:
#define NULL ((void *)0)
可以看到,C语言中NULL是一个void*指针,我们常用其来对指针变量进行初始化赋值或者作为返回类型为指针的函数的返回值(如函数执行失败时)等等。
(2)C++中的NULL
在C++中,NULL的定义如下:
`/* Define NULL pointer value */#ifndef NULL #ifdef __cplusplus #define NULL 0 #else /* __cplusplus */ #define NULL ((void *)0) #endif /* __cplusplus */#endif /* NULL */可以看到,在C++中,NULL被定义为0,而不是(void*)0,为什么C++在NULL的定义上不继续兼容C延续(void*)0的定义呢?因为C++中不能将void类型的指针隐式转换成其他指针类型,所以将NULL定义为(void)0的话并不能起到空指针的作用,如以下代码:
void hello(char* a) { printf("char\n");}
int main() { void* a = (void*) 0; char* b = a; //在C中可以,C++不行 hello(b); //在C中可以,C++不行 return 0;}因为C++中不能将void类型的指针隐式转换成其他指针类型,所以用(void**)0对其他类型指针赋初值是不行的。既然(void)0不能起到空指针的作用,不如干脆将NULL定义为0,引入0来表示空指针,可以对各种类型的指针进行赋值。
(3)C++中的nullptr
用过C++的童鞋都知道C++中有个nullptr的关键字可以用作空指针,既然已经有了定义为0的NULL,为何还要nullptr呢?这是因为定义为0的NULL很容易引起混淆,尤其是函数重载调用时,比如说:
void hello(char* a) { printf("char\n");}void hello(int a) { printf("int\n");}
int main() { hello(NULL); //调用hello(int a)函数 return 0;}如果在C++中还是用NULL来用作空指针的话,我们会以为hello(NULL)调用的是hello(char* a),但实际上,因为NULL定义为0,所以调用的是hello(int a)。所以为了避免这种混淆,C++定义了nullptr关键字用作空指针。nullptr的使用
nullptr关键字用于标识空指针,是std::nullptr_t类型的(constexpr)变量。它可以转换成任何指针类型和bool布尔类型(主要是为了兼容普通指针可以作为条件判断语句的写法),但是不能被转换为整数。
char *p1 = nullptr; // 正确int *p2 = nullptr; // 正确bool b = nullptr; // 正确. if(b)判断为falseint a = nullptr; // 错误注意nullptr和NULL以及0在作为条件判断时值都为false,它们两两之间进行等于(==)判断时值为true。
3.void*指针。
void* 是一种特殊的指针类型,可用于存放任意对象的地址。一个 void* 指针存放着一个地址,这一点和其他指针类似。
在介绍 void 指针前,简单说一下 void 关键字使用规则:
如果函数没有返回值,那么应声明为 void 类型; 如果函数无参数,那么应声明其参数为 void;(常省略) 如果函数的参数或返回值可以是任意类型指针,那么应声明其类型为 void* ; void 的字面意思是“无类型”,void*则为“无类型指针”,void不能代表一个真实的变量,void体现了一种抽象。
(1)任何类型的指针都可以直接赋值给void指针, 且无需进行强制类型转换。
任何类型指针都可以直接赋值给void指针。
double obj = 3.14, *pd = &obj;void* pv = &obj; // 正确,void* 能存放任意类型对象的地址 // obj 可以是任意类型的对象pv = pd;
// 正确,pv 可以存放任意类型的指针(2)void指针并不能无需类型转换直接赋值给其他类型,如果要把 void 类型的指针赋值给其他类型的指针,需要进行显式转换。
(2)void指针并不能无需类型转换直接赋值给其他类型 如果要把 void 类型的指针赋值给其他类型的指针,需要进行显式转换。
double obj = 3.14, *pd = &obj;void *pv = &obj;
double *pd1 = pv; // 错误,不可以直接赋值double *pd2 = (double*)pv; // 必须进行显示类型转换cout func1(); ×aObj-> func2(); 正确三、将Const类型转化为非Const类型的方法
采用const_cast 进行转换。 用法:const_cast (expression) 该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
-
常量指针被转化成非常量指针,并且仍然指向原来的对象;
-
常量引用被转换成非常量引用,并且仍然指向原来的对象;
-
常量对象被转换成非常量对象。
四、使用const的一些建议
-
要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
-
要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
-
在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
-
const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
-
不要轻易的将函数的返回值类型定为const;
-
除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
-
任何不会修改数据成员的函数都应该声明为const 类型。
五、补充重要说明
类内部的常量限制:使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。
如何初始化类内部的常量:一种方法就是static 和 const 并用,在外部初始化,例如:
class A{ public: A() {}
private: static const int i;//注意必须是静态的!};
const int A::i=3;另一个很常见的方法就是初始化列表:
class A{ public: A(int i=0):test(i) {} private: const int i;};还有一种方式就是在外部初始化,
如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个volatile类类型的。
new返回的指针必须是const类型的。。