c++之变量与基本类型------const修饰指针详解.,类的常量成员函数
一、本文主要内容
1、基本的内置类型
2、变量
3、复合类型
4、const限定符详解
5、处理类型
二、基本内置类型
1、主要分为算数类型和空类型
空类型:不对应具体的值,仅使用在一些特殊的场合
类型 | 含义 | 最小尺寸 |
---|---|---|
bool | 布尔类型 | 未定义 |
char | 字符 | 8位 |
wchar_t | 宽字符 | 16位 |
char16_t | Unicode 字符 | 16位 |
char32_t | Unicode 字符 | 32位 |
short | 短整型 | 16位 |
int | 整型 | 16位 |
long | 长整型 | 32位 |
long long | 长整型 | 64位 |
float | 单精度浮点型 | 6位有效数字 |
double | 双精度浮点数 | 10有效数字 |
long double | 扩展精度浮点数 | 10有效数字 |
(1) c++语言的一些规则
一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少和一个long一样大。
(2) 带符号类型和无符号类型
通过在类型名之前添加unsigned来得到无符号类型。
注意:字符型被分为三种:char、signed char和unsigned char。类型char和类型signed char并不一样。
字符型虽有三种类型,表现形式却只有两种:带符号的和无符号的。类型char实际上会表现为另外两种中的一种,由编译器决定。
建议:如果需要使用一个不大的整数,那么明确指定它的类型是unsigned char或者signed char.执行浮点运算时选用double.
2、类型转换(重点)
对象的类型定义了对象能包含的数据和能参与的运算。
(1) 给某种类型的对象强行赋给了另一种类型的值时
· 把一个非布尔值的算术值赋给布尔类型,初始值为0则结果为false,否则结果为true
· 把一个布尔值赋给一个非布尔值时,初始值是false则结果为0,初始值为true则结果为1。
· 把一个浮点数赋给整数类型时,结果直接去掉小数部分,只包含整数部分(不进行4舍5入,3.9则取3)。
· 把一个整数赋给浮点数时,结果为小数部分记为0(3则为3.0)。如果该整数所占的空超过浮点数,精度可能损失。
· 当给无符号类型赋值一个超出它表示的范围时,结果是初始值对无符号类型表示数值总数取模后的余数。当给其赋值一个 负值时,其结果为总数加上该赋值(例子:给 unsigned char 变量赋值-1,则得到255,为256+(-1)=255)
· 当给一个带符号类型赋值一个超出它表示的范围时,结果是未定义的。
· 把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
/* 错误:变量u永远也不会小于0,循环条件一直成立 */
for(unsigned int i=10; i>=0; --u)
std::cout << u << std::endl;
千万不要混用有符号类型和无符号类型,常常会出现异常结果,因为有符号类型会自动转换成无符号值。
3、字面值常量
严格来说,十进制字面值不会是负值。-42的负十进制字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负值而已。
转义序列 : 如果反斜杠\后面跟着的八进制数字超过3个,只有前三个数字与\构成转义序列(\1234表示两个字符\123 和 4)。\x则要用到后面跟着的所有数字(\x1234表示一个16进制数)。
指定字面值的类型:
前缀 | 含义 | 类型 |
---|---|---|
u | Unicode 16 字符 | char16_t |
U | Unicode 32 字符 | char32_t |
L | 宽字符 | wchar_t |
u8 | UTF-8 (仅用于字符串字面值常量) | char |
后缀 | 最小匹配类型 |
---|---|
u or U | unsigned |
l or L | long |
ll or LL | long long |
后缀 | 类型 |
---|---|
f or F | float |
l or L | long double |
二、变量
变量提供一个具名的、可供程序操作的存储空间。
1、变量定义
基本形式:类型说明符 一个或多个变量名
(1) 初始值:在对象创建时获得了一个特定值,就说这个对象被初始化了
注意:在c++语言中,初始化和赋值是两个完全不同的操作。初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦掉,而以一个新值来替代。
(2)初始化的几种不同形式
· int units_sold = 0;
· int units_sold = {0};
· int units_sold{0};
· int units_sold(0);
(3) 默认初始化
在定义时没有指定初值,则变量被默认初始化。
如果是内置类型(不记得请往上看),定义与任何函数体之外的变量被初始化为0。定义在函数体内部的内置类型变量将不被初始化,即其值未定义。
类的对象如果没有显示的初始化,则其值由类确定。
2、变量声明和定义的关系
声明:使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。
定义:负责创建与名字关联的实体。
如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显示地初始化变量。
extern int i; // 声明 i 而非定义 i
int j; // 声明并定义j
// extern 语句如果包含初始值就不在是声明,而变成定义了
extern double pi = 3.1416; // 定义
note : 变量只可以定义一次,但是可以被多次声明。
三、复合类型
1、引用(必须初始化)---- ( & )
引用为对象起了另外一个名字,引用类型引用另外一种类型。当定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用,不能令引用重新绑定到另外一个对象上,所以引用必须初始化。
· 引用即别名----引用并非对象,相反的,引用只是为一个已经存在的对象所以的另外一个名字。
· 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定到一起,并且引用的类型和要与之绑定的对象的类型严格匹配。除了如下的两个例外:
· 引用:变量的别名,指向同一个内存(1) 在初始化常量引用(const int &sp = i;)时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。(后文有详解)
(2) 站位置------2018-6-16(后补)
函数参数采用引用传递方式的有点如下:
1、保证不产生副本,提高传递的效率,节省内存开销
2、可指定为const,保证不改变实参的值,保证引用传递的安全性
2、指针
(1) 指针和引用的区别:
<a> 指针本身是一个对象,允许对指针赋值和拷贝,在生命周期可以指向多个不同的对象(不包括常量指针);而引用不是一 个对象,只是一个别名。
<b> 指针不需要在定义时必须赋初值;而引用必须初始化。
(2) 所有的指针的类型都要和他所指向的对象严格匹配,除了下面的两个例外:
<1> 允许令一个指向常量的指针指向一个非常量对象。
<2> 占位置------2018-6-16
(3) 有时候不明白一条语句到底改变了指针的值还是改变了指针所指对象的值,最好的办法就是记住赋值永远改变的是等号左侧的对象。
·任何非0指针对应的条件值都是true.
(4) void *指针是一种特殊的指针类型,可用于存放任意对象的地址。一般拿其和别的指针作比较,作为函数的输入和输出。但是注意:不能直接操作void * 指针所指的对象,必须对其进行显示类型转换才能进行操作。
· 引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。
tip:面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。
四、const限定符详解
当你希望定义一种值不能改变的变量时,可以使用const对该变量的类型进行限定(变成常量)。
// 把bufsize定义为常量,即不能修改它的值
const int bufsize = 512;
bufsize = 520; // 错误:bufsize是一个整型常量,不能修改其值。
· 因为常量创建后就不能改变其值,所以const对象必须初始化(引用也必须初始化).
· 与非const类型的所能参与的操作相比,const类型的对象只能执行不改变其内容的操作。
· 默认状态下,const对象仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同的文件中分别定义了独立了的变量。当然,如果想在一个文件中定义,在多个文件中使用,则可以通过加extern关键字。但是需要注意:必须在定义和声明都要加上extern关键字(与普通变量不一样)。
// file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问。
extern const int bufsize = 10; // const对象必须初始化
// file_1.h 头文件
extern const int bufsize; // 与file_1.cc中定义的bufsize是同一个
像上面的方法一样,只要在其他文件想用bufsize则包含其头文件即可。
1、const的引用------常量的引用
对常量的引用,不能被用作修改它所绑定的对象
const int ci = 1024;
const int &r1 = ci; // 正确:引用及其对象都是常量
r1 = 42; // 错误:r1是对常量的引用
int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象(类型不匹配)
引用的类型必须与其所引用的对象的类型一致。但有两个例外:
· 第一个就是在初始化常量引用时允许用任意的表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
请看例子分析:
double dval = 3.14; // double型变量
const int &ri = dval; // 用双精度浮点型变量dval给整型常量引用进行初始化
一看上面的代码,发现引用与其引用的对象的类型并不一致,一个为整型常量引用,而一个为双精度变量,其实上面的代码在编译器编译时就把第二行代码改成了如下:
const int temp = dval; // 由双精度浮点数生成一个临时的整型变量
const int &ri = temp; // 让ri绑定到这个临时变量
所以在这个情况下,ri 绑定了一个临时量对象。所谓临时量对象就是当编译器需要一个空间在暂存表达式的求值结果时临时创建的一个未命名的对象。
对const的引用可能引用一个非const对象
int i = 520;
int &r1 = i; // 引用r1绑定i对象
const int &r2 = i; // 常量引用r2也绑定到i对象上,但不允许通过r2修改i的值
r1 = 0; // r1非常量引用,可以修改i的值
r2 = 0; // 错误:r2是一个常量引用。
对于常量引用,其实就是绑定到临时量上,所以不能对临时量进行修改其的操作。
2、指针和 const
指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14; // pi是个常量,它的值不能改变
double *ptr = π // 错误:ptr是一个普通指针
const double *cptr = π // 正确:cptr可以指向一个双精度常量
*cptr = 42; // 错误:不能给*ptr赋值
指针的类型必须与其所指对象的类型一致。除了两个例外。
第一个是允许令一个指向常量的指针指向一个非常量对象,和常量引用一样,指向常量的指针没有规定其所指的对象必须是一个常量,只是仅仅要求不能通过该指针改变对象的值,但并没有规定那个对象的值不能通过其他方式改变。
· 常量指针必须初始化,而且一旦初始化完成,则其值(注意:指针的值指的是存放在指针中的地址)就不能改变。 *在const左边表示该指针是一个常量指针(例子:int *const iptr = &i;),地址不可变,值可变,也就是指向的值可以变,但是它的指向不能改变。
· 指向常量的指针即*在const右边(const int *ptr = &ci;),表示指向的值不能通过该指针来改变,而该指针的指向是可以改变的(存放在指针中的地址可以改变),地址可变,值不可变。
· 一个指针还可以是指向常量的常量指针,此时,则存放在指针中的地址不可变,指向的值也不可变。
3、顶层 const
用名词顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。
int i= 0;
int *const p1 = &i; // 不能改变p1的值,这是一个顶层const(指针本事是常量)
const int ci = 42; // 不能改变ci的值,这是一个顶层const
const int *p2 = &ci; // 允许改变p2的值(可以改变指向),这是一个底层const
const int *const p3 = p2; // 靠右的const是顶层const,靠左的是底层const
const int &r = ci; // 用于声明引用的const都是底层const
当执行拷贝时,常量是顶层还是底层有明显区别。
执行拷贝操作时不会改变拷贝对象的值,拷入还是拷出的对象是否是常量没什么影响。但底层const的限制却不能忽视,当执行对象的拷贝操作时,拷入和拷出的对象必须 具有相同的底层const,或者两个对象的数据类型必须能够转换。一般非常量可以转成常量,反之则不行。
4、类的常量成员函数
(1) 引入 this
// 有如下的结构体或者说类
struct Sales_data
{
std::string isbn() const
{
return bookNo;
}
std::string bookNo;
};
执行以下代码
struct Sales_data total;
total.isbn();
在上面的调用中,当 isbn 返回 bookNo 时,实际上它隐式地返回total.bookNo。
成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化 this 。
任何对类成员函数的直接访问都被直接看做 this 的隐式引用,上面就像书写了 this->bookNo 一样。
对于我们来说,this形参是隐式定义的。this 的目的总是指向“这个”对象,所以 this 是一个常量指针,即不允许改变this中保存的地址。
(2) const 成员函数
在上面的 isbn 函数后面紧跟着 const 关键字,此处 const 的作用主要是修改隐式 this 指针的类型。
默认情况下,this 的类型是指向类类型非常量版本的常量指针。意味着(在默认情况下)我们不能把 this 绑定到一个常量对象上,也就使得不能在一个常量对象上调用普通的成员函数。为解决这个问题,c++语言允许把 const 关键字放在成员函数的参数列表之后,此时,紧跟在参数列表后面的 const 表示 this 是一个指向常量的常量指针。像这样使用 const 的成员函数叫做常量成员函数。
// 所以上面的代码,就可以等效为下面的伪代码
std::string Sales_data::isbn(const Sales_data *const this)
{
return this->bookNo;
}
小结:
const常用修饰普通变量和指针变量
修饰变量名,表示常变量,不能修改常量值
修饰*,表示该指针变量指向为常量,不能通过该指针变量修改指向的内容
1、int * const p = &n; // 修饰指针变量名,表示常变量,不能修改常量值(可改内容,不可改地址,即不能对p进行赋值)
2、int const *q = &n; //这两个具有相同的功能,const修饰*,
const int *p = &n; //表示指向常量,不能通过q指向的内容(可改地址,不可改内容,即不能对*P进行赋值)
注:当const在*左边时如2,修饰指向的内容,即内容为常量;
当const在右侧时如1,修饰变量本身,即指向不可变
const的作用
1、阻止变量改变,通常声明直接初始化,以后不能再改变
2、对于指针,可指定指针本身为const,也可指定指向内容为const,还可两同时都为const
3、在函数定义,修饰参数,表示在函数内部本能改变其值
4、对于类成员函数,指定为const,则表明为一个常成员函数,不能修改类的数据成员
(声明样式为: 返回类型 <类标识符::>函数名称(参数表) const)
5、对于类的成员函数,有时必须指定其返回值为const类型,以使其返回值不为“左值”