C++11 constexpr和const的区别详解
constexpr 是 C++ 11 标准新添加的关键字,在此之前(C++ 98/03标准)只有 const 关键字,其在实际使用中经常会表现出两种不同的语义;
const并不能代表“常量”,它仅仅是对变量的一个修饰,告诉编译器这个变量只能被初始化,且不能被直接修改(实际上可以通过堆栈溢出等方式修改)。而这个变量的值,可以在运行时也可以在编译时指定。
constexpr可以用来修饰变量、函数、构造函数。一旦以上任何元素被constexpr修饰,那么等于说是告诉编译器 “请大胆地将我看成编译时就能得出常量值的表达式去优化我”。
const int func() {
return 10;
}
main(){
int arr[func()];
}
//error : 函数调用在常量表达式中必须具有常量值
对于func() ,胆小的编译器并没有足够的胆量去做编译期优化,哪怕函数体就一句return 字面值;
constexpr func() {
return 10;
}
main(){
int arr[func()];
}
//编译通过
则编译通过
编译期大胆地将func()做了优化,在编译期就确定了func计算出的值10而无需等到运行时再去计算。这就是constexpr的第一个作用:给编译器足够的信心在编译期去做被constexpr修饰的表达式的优化。
constexpr表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。声明为constexpr的变量一定是一个const变量,而且必须用常量表达式初始化:
constexpr int mf = 20; //20是常量表达式
constexpr int limit = mf + 1; // mf + 1是常量表达式
constexpr int sz = size(); //之后当size是一个constexpr函数时才是一条正确的声明语句
指针和constexpr
必须明确一点,在constexpr声明中如果定义了一个指针,限定符conxtexpr仅对指针有效,与指针所指的对象无关。
const int*p = nullptr; //p是一个指向整形常量的指针
constexpr int* q = nullptr; //q是一个指向整数的常量指针
p是一个指向常量的指针,q是一个常量指针,其中的关键在于constexpr把它所定义的对象置为了顶层const。并且,constexpr指针所指变量必须是全局变量或者static变量(既存储在静态数据区的变量)。
constexpr的好处:
- 是一种很强的约束,更好地保证程序的正确语义不被破坏。
- 编译器可以在编译期对constexpr的代码进行非常大的优化,比如将用到的constexpr表达式都直接替换成最终结果等。
- 相比宏来说,没有额外的开销,但更安全可靠。
class Solution {
private:
// 这里我用const就不可以,而用constexpr就可以
static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
};
非引用性参数,函数始终值传递
如果函数的形参不加引用类型,那么无论参数是变量还是地址,都是以值的形式传递;在做下面这道题的时候,加深了这一块的知识:
利用递归解题:
class Solution
{
public:
ListNode *rev(ListNode *head, ListNode *&new_head)
{
if (head->next == nullptr)
{
new_head = head;
return head;
}
ListNode *cur = rev(head->next, new_head);
cur->next = head;
return head;
}
ListNode *reverseList(ListNode *head)
{
if(head==nullptr)
return nullptr;
ListNode *new_head = nullptr;
rev(head, new_head)->next = nullptr;
return new_head;
}
};
对于函数 rev ,如果形参new_head前面不加引用类型,那么传递的是地址的值,也就是说,随着递归一次次开辟栈,栈局部变量 new_head 仅仅会在进行到最后一个结点时,将自己的地址值修改为 head ,此时 return,那么栈清空,变量释放,回到上一个栈是 new_head仍为 nullptr;
如果加了 & 引用类型,如果 rev 调用多少次,都是用的new_head本身,并不会在栈中创建和它相等的局部变量;
当然,这并不否认可以通过函数传指针来交换两个变量这一结果:
void fun(int *a,int *b)
{
int temp=*a;
*a=*b;
*b=*a;
}
calloc和malloc的区别
从调用形式上来看
(类型说明符*) malloc (size) 功能:在内存的动态存储区中分配一块长度为"size" 字节的连续区域。函数的返回值为该区域的首地址。 “类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如: pc=(char *) malloc (100); 表示分配100个字节的内存空间,并强制转换为字符数组类型, 函数的返回值为指向该字符数组的指针, 把该指针赋予指针变量pc;
calloc 也用于分配内存空间。调用形式: (类型说明符*) calloc(n,size)功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。例如: ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps;
从内存分配上来看
两者都是动态分配内存;malloc不初始化分配的内存,已分配的内存中可以是任意的值;而calloc初始化已分配的内存为0;
malloc不会初始化所分配的空间。如果你所申请的空间不释放,不被其他地方再次申请就不会有什么后果。如果你申请的空间进行一些写操作之后(空间里的内容已改变)你再释放再申请给其他指针使用(没有手动初始化)就可能导致程序崩溃;
异常处理