变量和基本类型——复合类型,const限定符,处理类型
一、复合类型
复合类型是指基于其他类型定义的类型。C++语言有几种复合类型,包括引用和指针。
1、引用
引用并非对象,它只是为一个已存在的对象所起的另外一个名字。
除了以下2种情况,其他所有引用的类型要和与之绑定的对象严格匹配,引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起。
引用的类型和绑定的对象不严格匹配的情况:
情况1:在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。允许为一个常量引用绑定非常常量的对象、字面值、一般表达式(此时,引用其实是绑定了一个临时量对象)。
#include <iostream>
#include <memory>
#include <string>
#include <vector> int main()
{
int i = ;
const int &r1 = i;
const int &r2 = 42.5;
const int &r3 = r1 * ;
return ;
}
必须认识到,常量引用仅对引用可参与的操作做出了限定,对于引用本身是不是一个常量未做限定。因为对象也可能是一个非常量,所以允许通过其他途径改变它的值。
情况2:可以把基类的引用绑定到派生类对象上。
2、指针
指针是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。指针无须在定义时赋初值。和其他内置类型一样,在块级作用域内定义的指针如果没有被初始化,值是未定义的。
如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问该对象,对指针解引用会得出所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值。
除了以下2种情况,其他所有指针的类型要和它所指向的对象严格匹配。
指针的类型和对象不严格匹配的情况:
情况1:允许一个指向常量的指针指向一个非常量对象,但是不能通过该指针去改变这个非常量对象的值,该对象的值只能通过其他途径修改。
#include <iostream>
#include <memory>
#include <string>
#include <vector> int main()
{
const double pi = 3.14;
const double *cptr = π // cptr是一个指向常量的指针
double dval = 3.14;
cptr = &dval;
//*cptr = 2.33; // 错误
return ;
}
情况2:可以将基类的指针绑定到派生类对象上。
1)其他指针操作
只要指针拥有一个合法值,就能将它用在条件表达式中。如果指针的值是0,条件取false;任何非0指针对应的条件值都是true。
#include <iostream>
#include <memory>
#include <string>
#include <vector> int main()
{
int *p1 = nullptr;
if (p1)
std::cout << true << std::endl;
else
std::cout << false << std::endl; int x = ;
p1 = &x;
if (p1)
std::cout << true << std::endl;
else
std::cout << false << std::endl; *p1 = -;
if (p1)
std::cout << true << std::endl;
else
std::cout << false << std::endl;
return ;
}
对于两个类型相同的合法指针,可以用相等操作符(==)或不相等操作符(!=)来比较它们,比较的结果是布尔值。如果两个指针存放的地址值相同,则它们相等;反之,它们不相等。这里两个指针存放的地址相同(两个指针相等)有三种可能:它们都为空、都执行同一个对象,或者都指向了同一个对象的下一地址。需要注意的是,一个指针指向某对象,同时另一个指针指向另外对象的下一地址,此时也有可能出现这两个指针相同的情况,即指针相等。
2)void*指针
void*是一种特殊的指针类型,可以存放任意对象的地址。利用void*指针能做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋值给另外一个void*指针。不能直接操作void*指针,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
3、理解复合类型的声明
1)指向指针的指针
一般来说,声明符中修饰符的个数没有限制。当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。
#include <iostream>
#include <memory>
#include <string>
#include <vector> int main()
{
int val = ;
int *p = &val; // p指向一个int型的数
int **pp = &p; // pp指向一个int型的指针
std::cout << *p << ", " << **pp << std::endl;
return ;
}
2)指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。
#include <iostream>
#include <memory>
#include <string>
#include <vector> int main()
{
int i = ;
int *p; // p是一个指针
int *&r = p; // r是一个对指针p的引用 r = &i; // r引用了一个指针,因此该语句时令p指向i
*r = ; // 解引用r得到i,也就是p指向的对象,将i的值改为0
std::cout << i << "," << *r << std::endl;
return ;
}
注意:面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清它的真实含义。
二、const限定符
当以编译时初始化的方式定义一个const对象时,编译器将在编译的过程中把用到该变量的地方都替换成对应的值。为了执行替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。
例子:
#ifndef FILE_H
#define FILE_H
void f();
#endif
file.h
#include <header/file.h>
#include <iostream> const int x = ;
void f()
{
std::cout << "func:&x " << &x << std::endl;
}
func.cpp
#include <iostream>
#include <string>
#include <header/file.h> const int x = ;
int main()
{
f();
std::cout << "main:&x: " << &x << std::endl;
return ;
}
main.cpp
x的地址完全不一样,说明2个x变量时独立的,不是同一个。
如果想要在不同的文件间共享同一个const变量怎么办,方法是对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就好了。
例子:
#ifndef FILE_H
#define FILE_H
extern const int x;
void f();
#endif
file.h
#include <header/file.h>
#include <iostream> extern const int x = ;
void f()
{
std::cout << "func:&x " << &x << std::endl;
}
func.cpp
#include <iostream>
#include <string>
#include <header/file.h> extern const int x;
int main()
{
f();
std::cout << "main:&x: " << &x << std::endl;
return ;
}
main.cpp
地址一样,说明是同一个变量。
1、const指针
允许把指针本身定为常量。常量指针必须初始化,一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量。
声明语句:基本数据类型 *const 指针名 = &var;
以这种形式声明一个指针就说明这个指针是一个常量指针,即不变的是指针本身,可以通过该指针修改指针所指向的对象的值。
2、顶层const
顶层const:表示指针本身是个常量。
底层const:表示指针所指的对象是一个常量。
一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用;底层const则与指针和引用等复合类型的基本类型部分有关;其中,指针类型既可以是顶层const又可以是底层const。
#include <iostream>
#include <string> int main()
{
int ci = ;
const int &r = ci; // 用于声明引用的const都是底层const
return ;
}
当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显。其中顶层const不受什么影响,底层const的限制不能忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之不行。
#include <iostream>
#include <string> int main()
{
int i = ;
const int ci = ; // 顶层const
const int *p2 = &ci; // 底层const
const int *const p3 = p2; // 左边的const是底层const,右边的是顶层const
//int *p = p3; // 错误:p3包含底层const的含义,而p没有
p2 = p3; // p2和p3都是 底层const
p2 = &i; // int*能转换成const int*
//int &r = ci; // 错误:普通的int&不能绑定到int常量上
const int &r2 = i; // 正确:普通的int可以绑定到const int&上
return ;
}
3、constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。
1)constexpr变量
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
#include <iostream>
#include <string> int f() {
return ;
}
int main()
{
constexpr int mf = ;
//constexpr int x = f(); // 错误:f不是constexpr函数
return ;
}
2)字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单、容易得到,就把它们称为“字面值类型”。
3)指针和constexpr
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
#include <iostream>
#include <string> int x = ;
int main()
{
constexpr int *p = &x; // p是一个指向整数的常量指针
return ;
}
一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
三、处理类型
1、类型别名
类型别名是某种类型的同义词:
#include <iostream>
#include <string> int main()
{
typedef char *ps; // ps是类型char*的别名
const ps p1 = ; // p1是指向char的常量指针
const ps *p2; // p2是一个指针,它的对象是指向char的常量指针
return ;
}
注意:遇到使用了类型别名的声明语句时,人们往往会错误地尝试把类型别名替换成它本来的样子去理解,这种理解方法是错误的;要将类型别名看成是一个基本数据类型去理解。
2、auto类型说明符
使用auto类型说明符能让编译器替我们去分析表达式所属的类型。编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。
#include <iostream>
#include <string> int main()
{
/*
当引用被用作初始值时,真正参与初始化的其实是引用的对象的值,
此时编译器以引用对象的类型作为auto的类型。
*/
auto i = , &r = i;
auto a = r; // a是一个整数
/*
auto一般会忽略掉顶层const,底层const会保留下来
*/
const int ci = i, &cr = ci;
auto b = ci; // b是一个整数(ci的顶层const特效被忽略掉了)
auto c = cr; // c是一个整数(cr是ci的别名,ci本身是一个顶层const)
auto d = &i; // d是一个整型指针
auto e = &ci; // e是一个指向常量的指针(对常量对象取地址是一种底层const)
/*
如果希望推断出的auto类型是一个顶层const,需要明确指出
*/
const auto f = ci; // ci的推演类型是int,f是const int
/*
可以将引用的类型设为auto,此时原来的初始化规则仍然使用;设置一个类型
为auto的引用时,初始值中的顶层const仍然保留。如果我们给初始值绑定一个引用,
则此时的常量就不是顶层const了。
*/
auto &g = ci; // g是一个整型常量引用,绑定到ci
//auto &h = 42; // 错误:不能为非常量引用绑定字面值
const auto &j = ; // 可以为常量引用绑定字面值
return ;
}
3、decltype类型说明符
decltype的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。
#include <iostream>
#include <string> int main()
{
const int ci = , &cj = ci;
decltype(ci) x = ; // x的类型是const int
decltype(cj) y = x; // y的类型是const int &,y绑定到变量x
//decltype(cj) z; // 错误:z是个引用,必须初始化
return ;
}
如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。有些表达式将向decltype返回一个引用类型。一般来说,当这种情况发生时,意味着该表达式的结果对象能作为一条赋值语句的左值。如果表达式的内容是解引用操作,则decltype将得到引用类型。
#include <iostream>
#include <string> int main()
{
int i = , *p = &i, &r = i;
decltype(r + ) b; // 加法的结果是int,因此b是一个未初始化的int
//decltype(*p) c; // 错误:c是int&,必须初始化
return ;
}
对于decltype所用的表达式来说,如果变量名加了一对括号,则得到的类型和不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。
#include <iostream>
#include <string> int main()
{
int i = ;
//decltype((i)) d; // 错误:d是int&,必须初始化
decltype(i) e; // e是一个未初始化的int
return ;
}
变量和基本类型——复合类型,const限定符,处理类型的更多相关文章
- 简谈const限定符
const修饰的数据类型是常量类型,常量类型的对象和变量在定义初始化后是不能被更新的.其实只用记住这一个概念,就可以明白const操作对象的方法. 1)定义const常量 最简单的: const in ...
- C++之const限定符
作者:tongqingliu 转载请注明出处: C++之const限定符 const初始化 const的特点: 用const加以限定的变量,无法改变. 由于const对象定义之后就无法改变,所以必须对 ...
- C++之const限定符(顶层const,底层const)
作者:tongqingliu 转载请注明出处:http://www.cnblogs.com/liutongqing/p/7050815.html C++之const限定符(顶层const,底层cons ...
- C++const限定符
在C语言中我们使用#define宏定义的方式来处理符号常量.而在C++中有一种更好的处理符号常量的方法,那就是使用const关键字来修改变量声明和初始化.这种处理常量方式的好处不言而喻:如果程序在多处 ...
- C++杂谈(一)const限定符与const指针
const限定符 c++有了新的const关键字,用来定义常变量,可以替C语言中的#define.关于const限定符,有以下需要注意: 1.创建后值不再改变 2.作用范围在文件内有效 3.添加ext ...
- C++ Primer 第二章 引用 指针 const限定符
1.引用: 为对象起了另外一个名字,引用类型引用另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d也就是声明的变量名(声明符就是变量名). PS:1.通过图片中编译所提示的报错信息 ...
- C++ const 限定符
C++ const 限定符 作用:把一个对象转换成一个常量 用法:const type name = value; 性质:1. 定义时必须初始化,定义后不能被修改.2. 类中的const成员变量必须通 ...
- const限定符的作用
const限定符的作用: 1.定义const常量:const可以将一个对象变成一个常量,不可被修改,所以定义的 时候必须进行初始 ...
- const限定符用法汇总
const限定符限定变量的类型是一个常量,对象一旦创建后其值就无法改变,所以const对象必须初始化. 初始化 const int i = get_size(); //运行时初始化 const int ...
随机推荐
- CentOS6.5下搭建ftp服务器(三种认证模式:匿名用户、本地用户、虚拟用户)
CentOS 6.5下搭建ftp服务器 vsftpd(very secure ftp daemon,非常安全的FTP守护进程)是一款运行在Linux操作系统上的FTP服务程序,不仅完全开源而且免费,此 ...
- jS冒泡优化
<script> //冒泡优化 将一个数组中的值从小到大排列 var arr=[65,85,12,36,75,46,50]; var sorted=true; ...
- linux基础07-bash编程(变量,变量类型)
(1)shell: 弱类型编程语言 强:变量在使用前,必须事先声明,甚至还需要初始化:弱:变量用时声明,甚至不区分类型: 变量赋值:VAR_NAME=VALUE (2)bash变量类型: 环境变量 本 ...
- SVN-版本控制工具安装与使用
什么是版本控制? 版本控制(Revision control)是一种软体工程技巧,籍以在开发的过程中,确保由不同人所编辑的同一档案都得到更新. 版本控制透过文档控制(documentation con ...
- CentOS 6.8 源码安装RabbitMQ
一.安装依赖环境 yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ ...
- MyBatis配置文件中的常用配置
一.连接数据库的配置单独放在一个properties文件中 之前,我们是直接将数据库的连接配置信息写在了MyBatis的conf.xml文件中,如下: <?xml version="1 ...
- 网络cmd命令
1.ping ip; 检测IP地址是否相通 ping命令的常用参数选项 ping IP -t:连续对IP地址执行ping命令,直到被用户以Ctrl+C中断. ping IP -l 2000:指定pin ...
- vs2015多行注释与取消多行注释
注释: 先CTRL+K,然后CTRL+C 取消注释: 先CTRL+K,然后CTRL+U
- spring ----> 搭建spring+springmvc+mybatis出现的几个问题
环境: idea ce 2018.1+maven3.5.3+mysql8.0.11+jdk1.8 spring4.3.7+spring mvc4.3.7+mybatis3.4.1+tomcat7.0. ...
- Confluence 6 修改导航显示选项
选择 子页面(Child pages)来在边栏中查看当前页面的子页面. 选择 页面树(Page tree)来查看整个空间的页面树,扩展当前的页面. 你也可以选择是否完全隐藏导航显示选项或者添加你希望可 ...