C++ 类型、类型转换
阅读原文时间:2023年07月15日阅读:2

C++ 数据类型

基本内置类型

字面值常量和字面值类型

类类型

隐式的类类型转换

聚合类

字面值常量类

constexpr 构造函数

类的静态成员

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。

您可能需要存储各种数据类型(比如字符型、宽字符型、整型、浮点型、双浮点型、布尔型等)的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么。

C++ 为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。下表列出了七种基本的 C++ 数据类型:

类型

关键字

布尔型

bool

字符型

char

整型

int

浮点型

float

双浮点型

double

无类型

void

宽字符型

wchar_t

其实 wchar_t 是这样来的:

typedef short int wchar_t;

所以 wchar_t 实际上的空间是和 short int 一样。

一些基本类型可以使用一个或多个类型修饰符进行修饰:

  • signed
  • unsigned
  • short
  • long

下表显示了各种变量类型在内存中存储值时需要占用的内存,以及该类型的变量所能存储的最大值和最小值。

注意:不同系统会有所差异。

类型

范围

char

1 个字节

-128 到 127 或者 0 到 255

unsigned char

1 个字节

0 到 255

signed char

1 个字节

-128 到 127

int

4 个字节

-2147483648 到 2147483647

unsigned int

4 个字节

0 到 4294967295

signed int

4 个字节

-2147483648 到 2147483647

short int

2 个字节

-32768 到 32767

unsigned short int

2 个字节

0 到 65,535

signed short int

2 个字节

-32768 到 32767

long int

8 个字节

-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807

signed long int

8 个字节

-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807

unsigned long int

8 个字节

0 到 18,446,744,073,709,551,615

float

4 个字节

+/- 3.4e +/- 38 (~7 个数字)

double

8 个字节

+/- 1.7e +/- 308 (~15 个数字)

long double

16 个字节

+/- 1.7e +/- 308 (~15 个数字)

wchar_t

2 或 4 个字节

1 个宽字符

从上表可得知,变量的大小会根据编译器和所使用的电脑而有所不同。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

C++中字面值常量和字面值类型 :

原文:https://blog.csdn.net/zhaojia92/article/details/50831436

一个形如42的值被称为字面值常量。字面值常量的形式和值决定了常量的类型。例如0x42是16进制表示的整型常量。‘a'是char型字面值。字面值常量顾名思义由字面意思表示,是常量。字面值常量在程序中是直接表示的,整型直接写出大小,字符直接写出字符。一个字面值常量在编译时被直接解析为立即数,编译器内部维护字面值常量的类型。

常量表达式是指在编译和执行过程中,该表达式的值不会改变,且编译过程中可以立即得到其值的表达式。一部分const对象是常量表达式,由常量表达式初始化的const对象也是常量表达式。常量表达式在程序运行时不会改变,即使一个程序多次启动或外部参数发生变化,该值也不会改变。编译器在编译优化时可能把常量表达式直接替换为立即数,具体要看编译环境。一般来讲,字面值常量属于常量表达式。

并非所有const对象都是常量表达式,const仅标记对象为只读属性,该对象在初始化后无法再改变。如果const对象所赋初值在编译阶段就可确定,那么此const对象才是常量表达式。const对象和存储位置也没有必然联系,常量可以分布在栈、堆、静态存储区中。对于声明在函数体内的const常量,如果没有被编译优化掉,该常量存储在栈中。全局的const常量存储在全局存储区。

C++中允许将变量声明为constexpr类型以使编译器在编译时检查该变量是否是常量表达式。声明为constexpr的对象一定是常量表达式。且初始化必须用常量表达式。

例如:

const int a = 12;    //a是常量表达式

const int b = a+1;  //b也是常量表达式

constexpr int c = a+b; //编译器可以在编译时期可以确定其值

const int d = getsize();   //c不是常量表达式,编译器编译时无法确知getsize()的执行结果。

constexpr int e = getsize();  //error! 将会报错

字面值的类型根据字面值表达形式不同而不同,编译器根据字面值形式推断字面值的类型。字面值多数为算术类型。自定义类、IO类不属于该类型。

对于指针字面值,其只有nullptr和NULL。指针也可以是constexpr的常量表达式,表示该指针为const且指向在编译时确定。不是所有的顶层const指针都是constexpr的。只有那些在编译时就确定地址指向的const指针才是constexpr。因此nullptr、NULL、指向固定地址的指针是constexpr的。

对于显式指出的字符串常量,其存储在常量存储区中,例如:

const char *const str1 = "hello"; //

const char *const str2 = "hello"; //“hello"存储在常量存储区

constexpr const char *str = str1; //constexpr,编译时确定的指针str

assert(str1 == str2); //str1和str2指向相同地址

此时的str1和str2指针都是constexpr的。那么,如何判断指针是否指向固定地址呢?

程序在内存中的组织形式是段,有堆栈段、数据段和代码段。对于数据指针指向数据,函数指针则指向某个代码。

对于定义在函数体外的变量,其指针是constexpr的;此类变量要占用数据段,而程序运行时,代码段和数据段大小位置均不会改变,因此编译器可以确定地址指向,是constexpr的。此外,函数内部定义的static静态变量,也会在数据区使用固定地址,因此指针也是constexpr的。对于定义在函数内部的变量,由于要在栈中开辟内存空间,而栈的情况要看程序运行状态,因此这类变量没有确定的地址,其指针不是constexpr的。代码段不会改变,函数指针也是constexpr的。

const/constexpr常量表达式可能在编译时直接替换为立即数;但也可能被编译为const对象,程序运行时占用内存空间。const/constexpr是否被替换为立即数或生成对象取决于编译环境和语义解析,语义不允许替换的地方便生成const对象,就如inline函数一样,不是所有inline函数都被替换。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

C++中的 字面值指定类型:

C++中只有内置类型存在字面值,没有类(class)类型字面值.
例如:0是int类型的字面值,3.14159是double类型的字面值.

字面值类型很多

整型浮点字面值

20                     // decimal   十进制

024                   //  octal     八进制

0X14                 //   hexadecimal     十六进制

128u ,128U        //    unsigned

1024UL              //    unsigned long

1L                     //      long  长整形

8Lu                   //      unsigned long

3.14159F = 3.14159E0F    // float  ,科学计数

.001f  = 1E-3F         // float,科学计数

12.345L = 1.2345E1L    // long double  扩展精度 ,科学计数

0.  = 0e0           // double ,科学计数

'a'              // char     字符

L'a'              // wchar_t   宽字符

//没有short的字面常量

字符串字面值

字符串字面值是一串常量字符,字符串字面值常量由双引号括起来,字符字面值常量用单引号括起来.

为了兼容C语言,C++中的字符串字面值将由编译器自动在末尾添加一个空字符串.

'A'  :     表示单个字符A

"A" :     表示字符A和空字符两个字符的字符串

同理 也存在宽字符串

L"a wide string literal"  : 一串宽字符+宽空字符

字符串字面值连接

不同类型的字符串不能连接: 字符串字面值 和宽字符串字面值不能连接

std::cout << "a multi-line "

                    "string literal "

                    "using concatenation"

                    << std::endl;

可以正确执行 : a multi-line string literal using concatenation

但是

std::cout << "multi-line " 

                    L"literal" <<std::endl;

将编译错误.

另外多行字面值还有一种写法:

std::cout << "a multi-line \

string literal \

using concatenation" << std::endl;

注意的是"\"必须是该行的末尾,后面不能有注释或者空格

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

类型转换: 在C++中, 如果两种类型相关联, 如果程序需要一种类型的运算对象时,可以用另一种关联类型的对象或值来代替。 也就是说,这两种类型可以互相转换, 即两种类型是关联的。

int  ival = 3.541+ 3;   编译器会警告 运算会损失精度

隐式类型转换: C++不会将两个数直接相加,  而是先根据类型转换规则 将对象转换成统一的类型后再求值。隐式类型转换: 这种类型转换是编译器自动执行, 不需要程序员介入

表达式中先把 int转换成double,  计算结果为double,  然后在把double转换成int  赋值给变量ival

隐式转换的情况: 下面 编译器会自动转换对象的 类型。

大多数表达式中,  比int小的类型首先提升成较大的整数类型

作为条件的表达式中, 非布尔值转换成布尔值

初始化中,

如果算术运算或关系运算的对象多种类型, 需要转换成同一种类型

C++常规类型自动类型转换规则

(1)如果有一个操作数的类型是long double,则将另一个操作数转换为long double。

(2)否则,如果有一个操作数的类型是double,则将另一个操作数转换为double。

(3)否则,如果有一个操作数的类型是float,则将另一个操作数转换为float。

(4)否则,到这里说明两个操作数都是整型,因此 进行整型提升。

(5)在这种情况下(俩操作数都是整形),如果两个操作数都是有符号或无符号的,且其中一个操作数的级别比另一个低,则转换为级别高的类型。

(6)如果一个操作数为有符号的,另一个操作数为无符号的,且无符号操作数的级别比有符号操作数高,则将有符号操作数转换为无符号操作数所属的类型。

(7)否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号操作数转换为有符号操作数所属的类型。

(8)否则,将两个操作数都转换为有符号类型的无符号版本。

算术转换: 是把一种算术类型转换成另一种算术类型。

定义了类型转换层次:运算对象转换成最宽的类型; 既有整数类型又有浮点类型时, 整数值转换成浮点类型。

整型提升:  负责吧小整数类型 转换成较大的整数类型。

对于bool/ char  signed char  unsigned char  short  unsigned short等类型来说, 只要他们所有可能的值都能存在int里,他们就会提升成int类型;否则 提升成unsigned类型。

较大的char类型(wchar_t  char16_t  char32_t)提升成 int   unsigned int    long  unsigned long  long long     unsigned long  long 中最小的类型, 前提是转换后的类型能容纳原类型所有可能的值。

无符号类型的运算对象:P142

要想理解算术转换,办法之一就是研究大量的例子。:

P142

其他隐式类型转换:几种情况:

1、  数组转换成指针: 在大多数用到数组的表达式中,  数组会自动转换成指向数组首元素的指针。

int ia[10];

int  *p=ia;

当数组被用作decltype 的参数, 或 作为 取地址符&   sizeof 或typeid的运算对象时,上诉转换不执行

指针转换: 常量整数值0 或字面值nullptr能转换成任意指针类型;  指向任意非常量的指针能转换成void* ; 指向任意对象的指针能转换成 const void *

转换成bool类型: 指针或  算术类型 的值为0   转换结果为false,  否则转换为true

转换成常量: 允许将 指向非常量类型的指针 转换成 指向相应的常量类型的指针。引用也是如此。

即  如果一个类型为T , 那么, 我们就能将 指向T的指针或引用 分别转换成指向const  T的指针或引用。但是反过来不存在, 因为试图删除底层const

int i ;

const int &j=i;

const int *p=&i;

int &r= j, *q=p;  错误  不能将const转换成非常量

类类型定义的转换:类类型能定义由编译器自动执行的转换。不过编译器一次只能执行一个种类类型的转换。

string s,  t="a  value";       // 字符串字面值转换成 string类型。

while( cin<< s)       IO库定义了istream向bool 转换的规则bool值由输入流的状态决定: 如果最后一次读入成功, bool值为true, 最后一次读取不成功,则 为false。

显示转换:

int  i, j;

double  slope = i / j;  使用这种方法将i和/ 或j 显式的转换成double, 叫强制类型转换

命名的强制类型转换:一个命名的强制类型转换具有以下 形式如下:

cast_name(expression);   type是目标类型, expression是要转换的值。 如果type是引用类型, 则结果是左值。

cast_name 包含:

static_cast    任何具有明确定义的类值转换, 只要不包含底层const, 都可以用static-cast

double slope = static_cast(j)/i;   //  将j强制转换成double类型进行浮点数计算,  i将被转换为double类计算。

static_cast 对于编译器无法自行执行的类型转换也很有用;例如: 我们用static_cast 找回存在于void* 指针中的值:

void  *p= &d;     任何非常量对象的地址都能存入void*

double  *dp =static_cast(p);  将void*转换会初始的指针类型。

dynamic_cast

const_cast :  只能改变运算对象的底层const

const  char  *pc;

char  *p = const_cast(pc);    对  但是通过p 写值是未定义的行为。

把常量转换成非常量的行为: 去掉 const性质( cast away the const)。一旦我们去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量, 使用强制类型转换获得写操作权限是合法的, 如果对象是个常量,再使用const_cast 执行写操作就会产生未定义的后果。

只有const_cast 能改变表达式的常量属性,其他都是未定义的。

不能用const_cast 改变表达式的类型。

reinterpret_cast 为运算对象的位模式提供较低层次上的重新解释。

建议: 避免强制类型转换。

旧式的强制类型转换:

早期版本C++中, 显式强制类型转换包含两种形式:

Type  (expr);     // 函数形式的强制类型转换

(type)  expr;    C语言风格的强制类型转换

隐式的类类型转换:

141页  为内置类型定义了几种自动转换的规则。同样,也为类定义隐式转换规则。

如果一个构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时把这种构造函数成为 转换构造函数

转换构造函数:  能通过一个实参调用的构造函数定义了 一条从 构造函数的参数类型向 类类型隐式转换的规则。

141页的类型转换中说: C++中 如果两个类型可以转换, 那么在程序中使用到一个类型时,可以用另一个类型直接作为替代,这样编译器会自动进行类型转换,内置类型没有限制转换次数,但是类类型的隐式类型转换只能进行一次。如果需要两次类类型转换,则必须有一个是显式类型转换。

string null_book = "9-999-9999-9";    //字符串为初始值。"9-999-9999-9"的类型是一个字符串, null_book的类型是string。

item.combine(null_book);        // 用实参string调用combine,是合法的,**编译器隐式地将string转换成一个临时Sales_data对象, 该临时量调用combine函数
**

// C++中,编译器只能 自动执行一步隐式地类类型转换

item.combine("9-999-9999-9");     //wrong,。 编译器不会进行两次类类型的隐式转换,即不会将字符串隐式转换成string,再将string转换成临时量Sales_data。

**// *上面的语句要想调用成功,** 必须字符串转换成string对象(编译器再隐式转换成Sales_data)或 Sales_data对象:*

item.combine( string("9-999-9999-9") );        // 显式 的转换成string,再隐式地转换成Sales_data

item.combine( Sales_data("9-999-9999-9")) ;    // 编译器先隐式的转换成string, 再显式的转换 成Sales_data

item.combine(cin);   //  使用 接受istream 的构造函数创建一个Sales_data临时量传递给combine。隐式把cin 转换成Sales_data, 执行了接受一个istream 的Sales_data构造函数。函数读取标准           //输入创建一个临时Sales_data对象,传递给combine,当combine完成后就不能访问这个临时对象。 先把他的值加到item中 ,随后将其丢弃。

抑制构造函数定义的隐式转换。: 在构造函数前加上 explicit(明确的,清楚的) 来阻止 隐式转换。 只能在类中声明构造函数时,使用explicit,在类外定义时不出现

此时没有任何构造函数能隐式的创建Sales_data对象, explicit只对一个实参的构造函数有效。需要多个实参的构造函数不能用于隐式转换,无须定义explicit。因为接受多个参数的构造函数不能用于执行隐式转换

class Sales_data {
public:
Sales_data() = default;
Sales_data(const std::string &s ,unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n){ }
explicit Sales_data(const std::string &s) :bookNo(s) { }  //给该构造函数 加上explicit关键字, 禁止该构造函数进行隐式的类类型转换
explicit Sales_data(std::istream &);              //也是explicit构造函数
// ……
};

此时:

item.combine(null_book);

item.combine(cin);         //之前的这两个调用都是错的  因为对应的两个构造函数都是explicit的, 不能用于隐式转换。

explicit 构造函数只能用于 直接初始化

发生隐式转换的一种情况是当我们执行拷贝形式的初始化时(使用=,此时, 我们只能用直接初始化(直接初始化可以用explicit构造函数), 而不能将 explicit构造函数用于拷贝形式的初始化过程:即,当我们用explicit关键字声明构造函数后, 它只能以直接初始化的形式使用,而且,编译器将不会在自动转换过程中使用该构造函数。

Sales_data item1(null_book);    // 正确,直接初始化创建对象。注意这里是显式地创建Sales_data对象并非用null_book 隐式的转换为Sales_data。

Sales_data item1 = null_book;    //  不能将explicit构造函数用于拷贝形式的 初始化过程。

为转换 显式的使用构造函数: 尽管编译器不能explicit构造函数 用于隐式转换过程, 但是我们可以使用这样的函数显式的强制进行类类型转换。

item.combine(Sales_data(null_book));     // 正确实参是显式构造的Sales_data对象:该调用通过接受一个string的构造函数创建了一个临时的Sales_data对象。

item.combine(static_cast(cin));    //  static_cast 可以使用explicit构造函数。使用sattic_cast 执行了显式而非隐式的转换。其中static_cast使用istream构造函数创建了一个临时                        的Sales_data对象。

// 标准库中的含有 显式构造函数的类。 我们用过的一些标准库中 含有单参数 的构造函数:

// 接受一个单参数的const char * 的string 构造函数,不是explicit的。   接受一个容量参数的vector构造函数 , 是explicit的。

聚合类:使得用户可以直接访问其成员,并且具有特殊的初始化语法形式

当一个类满足以下条件时,就是一个聚合类

1、 所有成员都是public 的

2、 没定义任何构造函数

3、 没有类内初始

4、 没有基类 ,也没有virtual函数, 虚函数

struct Data {

    int ival;

5     string s;

};

// 提供一个花括号括起来的成员初始值列表来给聚合类的数据成员初始化,初始顺序与类成员声明顺序一样和初始化数组成员的规则(101页)一样,如果初始化列表中初始值少于数据成员数量时,后面的成员值初始化。同时要注意 ,初始值绝不能超过类的成员数量

Data vall = { 0, "Anna" };   //vall.ival = 0,   vall.s = string("Anna").

显示地初始化类对象的成员存在三个明显的缺点:

1、  2、 3、

字面值常量类:

constexpr函数的参数和返回类型都必须是字面值类型的。除了算术类型、引用和指针外, 某些类也可以是字面值类型。

和其他类不同,字面值类型的类可能会含有  constepxr函数成员, 它必须符合constexpr 函数的所有要求,字面值类和 他的constexpr函数成员都是隐式const的

数据成员都是字面值类型聚合类都是字面值常量类

如果一个类不是聚合类,只要符合下面的条件,也是字面值常量类:

1、 数据成员都必须是 字面值类型。

2、  类必须至少含有一个constexpr 构造函数。

3、  如果一个数据成员含有类内初始值, 则内置类型成员的初始值必须是一条常量表达式; 或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr 构造函数

4、   类必须使用 析构函数的默认定义,该成员负责销毁类的对象。

constexpr 构造函数 尽管构造函数不能是const的, 但是字面值常量类的构造函数可以是constexpr函数。一个字面值常量类至少含有一个constexpr构造函数。

constexpr构造函数可以声明成    =default的形式,或删除函数形式

否则, constexpr 构造函数必须 既符合构造函数的要求 ,又要符合 constexpr函数的要求

函数体一般是空的,

前置关键字 constexpr 就能声明一个constexpr构造函数

1、 constexpr 构造函数必须初始化所有数据成员, 使用初始值或者使用constexpr函数或者使用一条常量表达式初始化。

2、 constexpr构造函数用于 生成constexpr对象 以及 constexpr函数的参数或返回类型。

*/

class   Debug {

public:

constexpr Debug(bool b = true): hw(b,) io(b), other(b) { }  //constexpr构造函数

constexpr Debug(bool h,bool i, bool o): hw(h), io(i), other(o) { }

constexpr bool any() { return hw || io || other; }

void set_io(bool b) { io = b; }

void set_hw(bool b) { hw = b; }

void set_other(bool b) { hw = b; }

private:

bool hw;            //硬件错误,非IO错误

bool io;             // IO错误

bool other;         // 其他错误。

};

constexpr Debug io_sub(false, true, false);    // 调试IO,创建Debug对象

if (io_sub.any())                                   // 等价于 if(true)

cerr << " print appropriate  error message" << endl;

constexpr  Debug prod(false);                         // 无调试,创建对象

if (prod.any())                                     // 等价于if(false)

cerr << "print  an error  message" << endl;

类的静态成员: 有时候类需要它的一些成员与类本身直接相关而不是与类的各个对象保持联系,对象不包含任何与静态数据成员有关的数据可以是数据成员也可以是函数成员。 与类本身关联,而不是与类对象关联, 可以是private  或public的。

静态成员的类型可以是常量、引用、指针、类类型等。

静态成员和普通成员的区别:

普通成员:与类的对象关联, 是某个具体对象的组成部分; 不可作为默认实参

静态成员:不从属于任何具体的对象,由所有类的对象共享。可作为默认实参。

static关键字只能在类内部使用, 在类外部定义静态成员时, 不能再加上使用static

静态成员函数不与任何对象绑定在一起, 不包含this指针。所以不能声明为const的, 并且不能在static函数体内显示使用this ,这一限制既适用于this 的显示使用, 也对*this调用非静态成员 的this的隐式使用有效,即,因为静态成员函数没有this,所以你直接使用成员名字,就不知道这个成员是属于哪个对象的成员,因为静态成员不从属于任何对象。

在成员函数体内:

显示使用this: 即直接使用this名字,

隐式使用this:在成员函数体内直接使用类成员名字,隐式使用了*this对象: *this.成员名

class Account {
public:
void calculate() { amout += amout* interestRate; }
static double rate() { return interestRate; } //静态成员函数
static void rate(double); //重载的静态成员函数
private:
std::string owner;
double amount;
static double interestRate; //静态数据成员
static double initRate();
};

访问使用静态成员:

虽然静态成员不属于任何对象,但是我们仍然可以使用类的对象、引用或指针来访问静态成员:

1  用作用域运算符::直接访问 静态成员

3、 虽然不属于类对象,但是仍然 能使用类对象引用或指针访问 静态成员。

double  r;

r = Account::rate();   //1  用作用域运算符::直接访问 静态成员。

Account  ac1;

Account *ac2 = &ac1;

            // 调用静态成员函数rate的等价形式

r = ac1.rate();        //过Account的对象或引用

r = ac2->rate();      //通过指向Account对象的指针

2  成员函数不用通过作用域运算符就 可以直接使用 静态成员。:

Class  Account

{

Public:

    void calculate() { amout += amout* interestRate; }    //成员函数不用通过作用域运算符就能直接使用 静态成员。

Private:

    Static   double interestRate;

   // 其他成员与之前版本一致

}

定义静态成员:

和其他成员函数一样,既可以在类内部也可以在类外部定义静态成员函数。当在类外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句中

因为静态数据成员不属于任何一个对象,所以他们并不是在创建类对象时被定义的。意味着它们不是由类的构造函数初始化的。 一般说来,我们不能在类内部初始化静态成员。相反的,必须在类外部定义和初始化 每个静态成员。和其他对象一样, 一个静态数据成员只能定义一次。

tip:要想确保对象只定义一次,最好的办法是把静态数据成员的定义和 其他 非inline函数的定义 放在同一个文件中。

类似全局变量,静态数据成员定义在任何函数之外。因此一旦它被定义了。就将一直在于程序的整个生命周期中。

// 类外部定义静态成员:

void  Account:: rate(double newRate)    //和类的所有成员一样,当指向类外部的静态成员时, 必须指明成员所属的类名。

{

interestRate = newRate;

}

定义静态数据成员的方式和 在类的外部定义成员函数差不多。指定对象的类型名、然后类名、作用域运算符以及成员自己的名字:

Double  Account::interestRate = initRate();    // 这条语句定义了名为interestRate的对象,该对象是类Account的静态成员, 其类型是double。 从类名开始,这条定义语句的剩余部分                            就都位于类的作用域中了。 因此,可以直接使用initRate函数。注意,虽然initRate是私有的, 我们也能用它初始化interestRate。                             和其他成员的定义一样, interestRate的定义也可以访问类的私有成员。

静态成员的类内初始化: 通常类的静态成员不在类内初始化。然而,我们可以为静态成员提供const整数类型的类内初值,不过要求静态成员必须是字面值常量类型的constexpr类型(常量表达式) 267页。初始值必须是常量表达式,因为这些成员本身就是常量表达式,所以他们可以用在所有适合于常量表达式的地方。

例如: 我们可以用一个初始化了的静态数据成员指定数组成员的维度:

class Account {
pulbic:
static double rate(){return interestRate}
static void rate(double);
private:
static constexpr int period = ; // 静态成员在类内被提供初始值。period 是常量表达式。
double daily_tbl [period]; //类内提供初始值的静态成员作为 数组维度
};

如果某个静态成员的应用场景仅限于编译器可以替换它的值的情况,则一个初始化的const或 constexpr   static不需要分别定义。相反,如果将它用于值不能替换的场景,则该成员必须在类外有一条定义语句和初始化。

例如 如果period的唯一用途就是定义daily_tbl的维度,则不需要在Account外面专门定义period。此时如果我们忽略了这条定义,那么对程序非常微小的改动也可能造成编译错误,因为程序找不到该成员的定义语句。 举个例子,当需要把Account::period传递给一个接受const int&的函数时,必须定义period。

如果类的内部提供了一个初始值,则成员的定义不能再指定一个初始值了:

//一个不带初始值的静态成员的定义。

constexpr int Account::period; // 初始值在类的定义内提供

好习惯: 即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员,在类外部 进行不带初始值的静态成员定义。

静态成员适用于某系场景,而普通成员不能:

1、前面说股, 在类完成前, 非静态数据成员不能是它所属类的类型,但是static数据成员的类型可以是所属类型的类类型。 静态数据成员,只能声明成他所属类的引用或指针

2、 static成员和普通成员另一个区别: static成员可以作为默认实参 ,非static不能为默认实参

class Bar {

public:

//…….

private:

    static Bar mem1;   // 对   static  可以是不完全类型

    Bar \*mem2;          //对    指针可以是不完全类型

    Bar mem3;             // 错 数据成员必须是完全类型。

};

静态成员和 非静态成员的另一个区别是: 可以使用静态成员作为默认实参:

非静态成员不能作为默认实参,因为他的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。

class Screen {

public:

         // bkground表示一个在类中稍后定义的静态成员

    Screen &  clear(char = bkground);   // static 成员作为默认实参

private:

    static const  char bkground;  

};