c/c++/c++11/c++14 static const constexpr区别

c/c++/c++11/c++14 static const constexpr区别

  • c/c++/c++11/c++14 static const constexpr区别

    • C
      • static 修饰局部变量
      • static 修饰全局变量或者函数时
    • C++98
      • static 修饰类内变量
      • static const 修饰类内变量
      • static 修饰类内函数
      • const 修饰类内变量
      • const 修饰类内函数
      • const 修饰对象
    • C++11/C++14
      • constexpr 修饰普通变量
      • constexpr 修饰函数
      • constexpr 修饰类的构造函数
      • constexpr 修饰模板函数
    • 参考网址
  • 常量 const

  • 常量表达式 constexpr

  • 静态变量 static

C

  • static 在 C 语言中主要是两种用法
    • 修饰局部变量
    • 修饰函数和全局变量

static 修饰局部变量

  • 特点: static局部变量的”记忆性”与生存期的”全局性”
    • “记忆性”, 在第二次调用进入时, 能保持第一次调用退出时的值.程序运行很重要的一点就是可重复性, 而static变量的”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
    • “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 —- 不可重入性
  • 局部变量按照存储形式来分,分为auto,static,register
  • 首先从内存四区的角度去看,auto即为普通的局部变量,存储在栈上,当函数结束时,随之释放。
  • register为寄存器变量,存放在寄存器里面,调用速度快。
  • 在C语言中register变量不能取地址,会报错。而在c++中,对register做了增强,党C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。
  • static修饰局部变量时该变量是存放在静态存储区,生命周期是整个程序结束。
  • static局部变量初次定义时就要初始化,且只能初始化一次。如果在定义的时候不初始化,编译器就会自动赋值为0;
  • 也就是说如果重复调用同一个函数,在第二次调用时,就不会再执行static局部变量初始化那句话了
void staticLocalVar(){
  static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作
  printf(“%d”,a);
  a++;
}

int main(){
  staticLocalVar(); // 第一次调用, 输出a=0
  staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
  return 0;
}

static 修饰全局变量或者函数时

  • 不是为了限制其存储方式,而主要是为了限制该全局变量或者函数的作用域仅限于本文件,所以又称为内部函数。
  • 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文)内部.
  • 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

C++98

// .h文件
class BClass{
    public:
        BClass();
    private:
        int i; // 普通成员变量
        const int ci; // 常量成员变量
        int &ri; // 引用成员变量
        static int si; // 静态成员变量
        //static int si2 = 100; // error: 只有静态常量成员变量,才可以这样初始化
        static const int csi; // 静态常量成员变量
        static const int csi2 = 100; // 静态常量成员变量的初始化(Integral type) (1)
        static const double csd; // 静态常量成员变量(non-Integral type)
        //static const double csd2 = 99.9; // error: 只有静态常量整型数据成员才可以在类中初始化
};

// .cpp文件

// 静态成员变量的初始化(Integral type),不需要static
int BClass::si = 0;
// 静态常量成员变量的初始化(Integral type),不需要static
const int BClass::csi = 1;
// 静态常量成员变量的初始化(non-Integral type),不需要static
const double BClass::csd = 99.9;

BClass::BClass() : i(1), ci(2), ri(i){ // 对于常量型成员变量和引用型成员变量,必须通过
    i = 5;  // 普通成员变量可以,常量型成员变量和引用型成员变量不能在这里初始化
}
  • static_cast、const_cast、reinterpret_cast和dynamic_cas

static 修饰类内变量

  • 实现了同一个类,多个对象共享数据,协调行为的目的(而const属于对象)。
  • 静态变量具有全局变量的优势,又不会像全变量一样被滥用。C++中用于管理静态变量,就需要用到静态函数。
  • static 成员类外存储,求类大小,并不包含在内;(而const会被计算)
  • static 成员的命名空间属于类的全局变量,存储在 data 区 rw 段,静态都将存储在全局变量区域
  • static 定义的静态常量在函数执行后不会释放其存储空间(const定义的常量在超出其作用域之后其空间会被释放)
  • 初始化如下(属于类,不属于对象)
    • 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆
    • 初始化时不加该成员的访问权限控制符private、public等
    • 初始化时使用作用域运算符来表明它所属的类,因此,静态数据成员是类的成员而不是对象的成员。

static const 修饰类内变量

  • 等价于 const static
  • 只有静态常量整型数据成员才可以在类中初始化(static和const都不行)

static 修饰类内函数

  • staitc 修饰成员函数,仅出现在声明处,不可在定义处(const需要两边都有)
  • 静态成员函数只能访问静态数据成员(因为无this指针)。
  • static 成员函数主要目的是作为类作用域的全局函数
  • 不能被声明为virtual(因为无this指针)

const 修饰类内变量

  • 修饰数据的数据成员,称为常数据成员。
  • const 常数据成员可被普通成员函数和常成员函数来使用,不可以更改
  • const定义的常量在超出其作用域之后其空间会被释放
  • const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。
  • 类可以创建多个对象,不同的对象其const数据成员的值可以不同
  • 初始化
    • 在初始化参数列表中初始化 const 成员的值(引用型成员变量类似),所以必须有构造函数
    • 构造函数内部,不能对const成员赋值,编译器直接报错
    • 构造函数列表初始化执行顺序与成员变量在类中声明相同,与初始化列表中语句书写先后无关
    • const 常数据成员在使用前必须被初始化。
  • 强制修改const变量,发生了什么情况(const_cast)?

const 修饰类内函数

  • const 修饰函数放在声明之后,实现体之前。void f() const {}
  • const 修饰的函数在声明处和定义处都需要 const 修饰符。
  • const 修饰函数不能修改类内的数据成员变量。why?
  • const 修饰函数只能调用 const 函数,非 const 函数可以调用 const 函数。
  • const 成员函数不能修改成员变量的值,但可以访问成员变量

const 修饰对象

  • 只能调用const成员函数。

C++11/C++14

  • C++ 本身已经具备了常量表达式的概念,比如 1+2, 3*4 这种表达式总是会产生相同的结果并且没有任何副作用。
  • 所谓常量表达式,指的就是由多个(≥1)常量组成的表达式
  • C++11 提供了 constexpr 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式
  • constexpr 关键字使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。
    • 常量表达式的计算往往发生在程序的编译阶段,但是也不一定,具体的计算时机还是编译器说了算。
    • 一个非常明显的例子就是在数组的定义阶段
    • 如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。
    • 常量表达式的应用场景有很多,比如定义数组时的长度,匿名枚举、switch-case 结构中的 case 表达式等
  • C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。
    • constexpr 修饰的函数可以使用递归,比如下边的 fibonacci 函数
  • 从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句
#include 

#define LEN 10

int len_foo() {
    int i = 2;
    return i; 
}

// constexpr 告诉编译器应该去验证 len_foo_constexpr 在编译期就是一个常量表达式。
constexpr int len_foo_constexpr() {
    return 5;
}

// C++11版本支持,constexpr 修饰的函数可以使用递归
constexpr int fibonacci(const int n) {
    return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
}

// C++14版本支持,从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句
constexpr int fibonacci(const int n) {
    if(n == 1) return 1;
    if(n == 2) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

int main() {
    char arr_1[10];  // 合法 
    char arr_2[LEN];  // 合法
    
    int len = 10;
    // char arr_3[len]; // 非法
    
    const int len_2 = len + 1;
    // char arr_4[len_2]; // 非法,数组中的长度,必须是一个常量表达式(而非常量)

    constexpr int len_2_constexpr = 1 + 2 + 3;
    char arr_4[len_2_constexpr]; // 合法

    // char arr_5[len_foo()+5]; // 非法,C++98 之前的编译器无法得知 len_foo() 在运行期实际上是返 回一个常数,这也就导致了非法的产生
    char arr_6[len_foo_constexpr() + 1]; // 合法

    std::cout << fibonacci(10) << std::endl;  // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
    return 0;
}

constexpr 修饰普通变量

  • C++11 标准中,定义变量时可以用 constexpr 修饰,从而使该变量获得在编译阶段即可计算出结果的能力。
  • 值得一提的是,使用 constexpr 修改普通变量时,变量必须经过初始化且初始值必须是一个常量表达式。如下实例:
#include 
using namespace std;

int main() {
    constexpr int num = 1 + 2 + 3;
    int url[num] = {1,2,3,4,5,6};
    couts<< url[1] << endl;
    return 0;
}
  • 将此示例程序中的 constexpr 用 const 关键字替换也可以正常执行,这是因为 num 的定义同时满足“num 是 const 常量且使用常量表达式为其初始化”这 2 个条件,由此编译器会认定 num 是一个常量表达式。这属于编译器的优化策略。
  • 当常量表达式中包含浮点数时,考虑到程序编译和运行所在的系统环境可能不同,常量表达式在编译阶段和运行阶段计算出的结果精度很可能会受到影响,因此 C++11 标准规定,浮点常量表达式在编译阶段计算的精度要至少等于(或者高于)运行阶段计算出的精度。(怎么保证呢???)

constexpr 修饰函数

验证结构都是在 Apple clang version 13.1.6 (clang-1316.0.21.2.5) 编译器下进行

  • constexpr 还可以用于修饰函数的返回值,这样的函数又称为“常量表达式函数”。
  • constexpr 并非可以修改任意函数的返回值。一个函数要想成为常量表达式函数,必须满足如下 4 个条件
    • 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句(C++14就可以定义局部变量了)。
    • 该函数必须有返回值,即函数的返回值类型不能是 void(C++11报错,C++14正常)
    • 普通函数在调用时,只需要保证调用位置之前有相应的声明即可;而常量表达式函数则不同,调用位置之前必须要有该函数的定义,否则会导致程序编译失败。(目前测试C++11和C++14都正常)
    • return 返回的表达式必须是常量表达式
    • 在常量表达式函数的 return 语句中,不能包含赋值的操作(例如 return x=1 在常量表达式函数中不允许的)
#include 

//可以添加 using 执行、typedef 语句以及 static_assert 断言,c++11和c++14都可以编译通过
constexpr int constexprNormal(int x) {
    return 1 + 2 + x;
}

// 标准C++11编译无法通过(优化后的正常但是会给警告),C++14可以
constexpr int constexprInnerPara(int x) {
    int ret = 1 + 2 + x;
    return ret;
}

// c++11报错,c++14不报错
constexpr void constexprVoid() {
}

int main() {
    std::cout << constexprNormal(5) << std::endl;
    std::cout << constexprInnerPara(5) << std::endl;
    constexprVoid();
}

constexpr 修饰类的构造函数

  • 对于 C++ 内置类型的数据,可以直接用 constexpr 修饰
  • 但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。
  • 当我们想自定义一个可产生常量的类型时,正确的做法是在该类型的内部添加一个常量构造函数。
  • constexpr 修饰类的构造函数时,要求该构造函数的函数体必须为空,且采用初始化列表的方式为各个成员赋值时,必须使用常量表达式。
  • constexpr 可用于修饰函数,而类中的成员方法完全可以看做是“位于类这个命名空间中的函数”,所以 constexpr 也可以修饰类中的成员函数
  • C++11 标准中,不支持用 constexpr 修饰带有 virtual 的成员方法。
#include 
using namespace std;

//自定义类型的定义
class myType {
public:
    constexpr myType(const char *name, int age) : name(name), age(age){};
    constexpr const char *getname() const { // C++11可以不加 const 修饰,但是C++14必须加,否则编译报错
        return name;
    }
    constexpr int getage() const {  // C++11可以不加 const 修饰,但是C++14必须加,否则编译报错
        return age;
    }

private:
    const char *name;
    int age;
    //其它结构体成员
};

int main() {
    constexpr struct myType mt { "zhangsan", 10 };
    constexpr const char *name = mt.getname();
    constexpr int age = mt.getage();
    cout << name << " " << age << endl;
    return 0;
}

constexpr 修饰模板函数

  • C++11 语法中,constexpr 可以修饰模板函数,但由于模板中类型的不确定性,因此模板函数实例化后的函数是否符合常量表达式函数的要求也是不确定的。
  • 针对这种情况下,C++11 标准规定,如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。
#include 
using namespace std;

//自定义类型的定义
struct myType {
    const char *name;
    int age;
    //其它结构体成员
};
//模板函数
template 
constexpr T dispaly(T t) {
    return t;
}

int main() {
    struct myType stu {
        "zhangsan", 10
    };
    //普通函数
    struct myType ret = dispaly(stu);
    cout << ret.name << " " << ret.age << endl;
    //常量表达式函数
    int a[dispaly(3)] = {1, 2, 3};
    cout << a << endl;
    return 0;
}
  • 可以看到,示例程序中定义了一个模板函数 display(),但由于其返回值类型未定,因此在实例化之前无法判断其是否符合常量表达式函数的要求:
  • dispaly(stu) 当模板函数中以自定义结构体 myType 类型进行实例化时,由于该结构体中没有定义常量表达式构造函数,所以实例化后的函数不是常量表达式函数,此时 constexpr 是无效的;
  • dispaly(10) 模板函数的类型 T 为 int 类型,实例化后的函数符合常量表达式函数的要求,所以该函数的返回值就是一个常量表达式。

参考网址

  1. constexpr
  2. c
  3. c++

你可能感兴趣的:(C++,c语言,c++,开发语言)