关闭

C++ primer读书笔记 2

标签: c++
150人阅读 评论(0) 收藏 举报
分类:

变量与基本类型

1.基本内置类型:算术类型和空类型
(1) 算术类型: 整型(包括字符型, 布尔型) 浮点型
基本字符类型是char, 一个char的空间确保可以保存机器基本字符集中任意字符对应的数字值. 一个char的大小与一个机器字节一样.

扩展字符集, wchar_r , char16_t , char32_t
wchar_t类型是用于确保可以存放机器最大扩展字符集中的任意一个字符
char16_t , char32_t类型是为unicode字符集服务

带符号类型和不带符号类型

类型int, long, long long都是带符号, 在前面加unsigned表示无符号整数.

字符型分为: char, signed char, unsigned char. 特别注意, 类型char和类型signed char并不一样, 尽管字符型有三种,但是字符的表示形式只有两种:带符号和无符号的. 类型char实际上表现为上述两种的一种,具体由编译器决定.

short 占16位, int 占16位, long 占32位, long long 占64位.

(2)类型转换

b. 布尔型->非布尔型: false为0, true为1;

c. 浮点数->整数: 保留小数点前部分;

e. 当我们赋给无符号类型一个超出它表示范围的值时, 结果是初始值对无符号类型表示数值总数取模后的余数.例如, 8 bit大小的unsigned char可以表示0~255的值, 如果我们赋给一个区间以外的值, 则实际的结果是该值对256取模后的余数. 所以把 -1 赋给8 bit大小的unsigned char 所得到的结果是255.

f. 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的.此时程序可能继续工作,可能崩溃,也可能产生垃圾信息.

建议: 避免无法预知和依赖于实现环境的行为

把负数转换成无符号数类似于直接给无符号数付一个负值,结果等于这个负数加上无符号数的模.

(3)字面值常量
整数和浮点型字符型

字符和字符串字面值
字符串字面值的类型实际上是由常量字符构成的数组.

转义序列
换行符 \n 横向制表符 \t 报警符 \a
纵向制表符 \v 退格符 \b 双引号 \”
反斜号 \ 问号 \? 单引号 \’
回车符 \r 进纸符 \f

2.变量
初始化:
事实上在c++中,初始化和赋值是两个完全不同的操作.
列表初始化:
初始化不同形式:

int units_sold=0;
int units_sold={0};
int units_sold{0};
int units_sold(0);

如果我们使用列表 {} 初始化且初始化存在丢失信息的风险,编译器将报错:

long double ld =3.1415926536;
int a{ld}, b={ld}; //错误, 转换未执行, 因为存在丢失信息的风险
int c(ld), d=ld; //正确, 转换执行, 且确实丢失了部分值

默认初始化
如果是内置类型的变量未被显示初始化,他的值由定义的位置决定.
定义在任何函数体之外的变量初始化为0.
定义在函数体内部的内置类型变量将不被初始化.
一个未被初始化的内置类型变量的值是未定义的(undefined),如果试图拷贝或其他类型访问此类型将引发错误.

3.变量声明和定义的关系

C++支持分离式编译机制, 该机制就是允许将程序分割成若干个文件,每个文件可被独立编译.

如果将程序分为多个文件,那么需要有文件间共享代码的方法. 例如, 一个文件的大妈可能需要使用另一个文件中定义的变量. 例如, std::cout和std::cin,他们定义在标准库中, 却被我们写的程序使用.

为了支持分离式编译, c++语言将声明定义分离开来,声明使得名字被程序所知, 一个文件如果想使用别处定义的名字则必须包括对这个名字的声明.定义是负责创建与名字相关联的实体.

变量声明规定了变量类型和名字, 在这一点上定义和它是相同的, 但是除此之外, 定义还申请了空间, 也可能为变量赋初始值

如果是想声明一个变量而不是定义,就在变量名前加一个关键字extern,而且不要显示的初始化变量;

extern int i; //声明i 而非定义i
int j;  //声明并定义j
任何包含显示初始化的声明都成为定义.
extern int i =1; //定义

变量能且只能被定义一次, 却可以被多次声明.

4.标识符

由字母, 数字和下划线组成, 由字母和下划线开头.长度无限制,大小写敏感.

5.复合类型

(1) 引用: 定义引用的时候,程序把引用和它的初始值绑定在一起,而不是把初始值拷贝给引用. 一旦初始化完成,引用将和它的初始值对象一直绑定在一起.因为无法令引重新绑定到另一个对象上,因此引用必须初始化.

引用即别名 引用并非对象, 它只是为一个已经存在的对象所起另一个名字
允许在一条语句定义多个引用,其中每个引用标识符都必须以符号&开头.

6.指针

用取地址符 & 来获取对象的地址;
用解地址符 * 来访问该对象;
生成空指针的方法:

  int *pr = nullptr;
  int *pr = 0;
  #include cstdlib
  int *pr= NULL;

void* 指针
void* 指针是一种特殊的指针, 可存放任意对象的地址. 一个void* 指针存放着一个地址

 double obj = 3.14, *pd = &obj;
 void *pv = &obj;
 pv = pd;

7.理解复合类型的声明

指向指针的引用:
引用本身不是一个对象,因此不能定义指向引用的指针.但指针是对象,所以存在对指针的引用.

  int i=1;
  int *p;
  int *&r=p; //r是一个对指针p的引用
  r= &i;  //r引用了一个指针, 因此给r赋值&i是令p指向i
  *r= 0;   //解引用r得到i, 也就是p指向的对象, 将i的值改为0;

要理解r的类型是什么,最简单的方法就是从右往左阅读r的定义. 离变量名最近的符号对变量的类型有最直接的影响,因此r是一个引用.

8.const限定符

默认状态下, const对象仅在文件内有效

当以编译时初始化的方式定义一个const对象时, 就如对bufSize的定义一样:

const int bufSize = 512; // 输入缓冲区大小

编译器将在编译过程把该变量的地方替换成对应的值.为了执行上述替换, 编译器必须知道变量的初始值.如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值,要做到这一点必须在每个用到变量的文件都有它的定义.为了支持这一用法,同时避免对同一变量的重复定义,默认情况下, const对象被设定为仅在文件内有效.当多个文件中同时出现const变量,其实等同于在不同的文件中分别定义了独立的变量.

某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实又必要在文件间共享.这种情况下,我们不希望编译器为每个文件分别生成独立的变量.相反, 我们想让这类const对象像其他(非常量)对象一样工作, 也就是说, 只在一个文件中定义const, 而在其他文件中声明并使用它.

解决方法就是:对于const变量不管是声明还是定义都添加extern关键字, 这样只需定义一次就行:

// file_1.cc定义并初始化了一个常量, 该常量能被其他文件访问
extern const int bufSize = fcn( );
// file_1.h 头文件
extern const int bufSize;//与file_1.cc中定义的bufSize是同一个

如上述程序, file_1.cc定义并初始化了bufSize, 因为这条语句包含了初始值, 所以它显然是一次定义, 然而, bufSize是一个常量, 必须用extern加以限定使其被其他文件使用.

9.const的引用

与普通引用不同, 对常量的引用不能被用作修改它所绑定的对象:

const int ci =1024;
const int &rl=ci;    //正确, 引用及其所对应的对象都是常量
rl=21;    //错误, rl是对常量的引用
int &r2=ci;   //错误, 试图让非常量引用指向一个常量对象

初始化和对初始化的引用
引用的类型必须与其所引用对象的类型一致, 但是有两个例外:
a.在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型:

   int i=42;
   const int & r1 = i;

b.对const的引用可能引用一个并非const的对象

常量的引用仅对引用可参与的操作做了限定,对于引用的对象本身是不是一个常量未做限定,因为对象也可能是个非常量,所以允许通过其他途径改变它的值.

int i=42;
int r1=i;
const int &r2=i;
r1=0;//正确
r2=0;//错误

10.指针和const

与引用一样,也可以令指针指向常量和非常量。
指向常量的指针不能用于改变其所指的对象的值.要想存放常量对象的地址,只能用指向常量的指针。

const double pi=3.14;
double *ptr=&pi ;//错误, ptr是一个普通的指针
const double *cptr=&pi ;//正确, cptr可以指向一个双精度的常量

指针的类型必须要与其所指的对象的类型一致,但是两种例外,第一个是允许令一个指向常量的指针指向一个非常量;和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量,所谓指向常量的指针仅仅是要求不能通过该指针改变指针所指向的对象,而没规定不能通过其他途径改变。

const指针

指针是对象而引用不是。
所以允许指针本身定为常量,常量指针必须初始化,而一旦初始化,则他的值(也就是它所存放的指针中的地址)就不能再改变。把* 放在const关键字之前也就是用以说明指针是一个常量,这样的书写格式隐含一层意味,即不变的是指针本身的值不是指向的那个值。

int errNumb=0;
int *const curErr= &errNumb; //curErr是一直指向errNumb
const double pi=3.14;
const double *const pip=& pi ;//pip是一个指向常量对象的常量指针

顶层const

指针本身是一个对象,它可以指向另一个对象。所以,指针本身是不是常量以及指向的是不是一个常量就是两个相互独立的问题

顶层表示指针本身是一个常量
底层表示指向的对象是一个常量

用于声明引用的const都是底层const

11、constexpr和常量表达式

常量表达式指的是值不会改变并且在编译过程就能得到计算结果的表达式。
字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

一个对象是不是常量表达式由它的数据类型和初始值决定

const int max=20//常量表达式
const int limit =max +1//常量表达式
int staff=1//不是常量表达式
const int sz=get_size( );
//不是常量表达式,虽然sz是常量,但是具体值需要到运行时才会获取到,所以不是常量表达式

constexpr常量
c++11标准,允许将变量声明为constexpr类型,以便由编译器来验证变量的值是否为一个常量表达式。

声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化

constexpr int mf=20//20是一个常量表达式
constexpr int limit=mf+1//mf+1是常量表达式
constexpr int sz=size();//只有当size是一个constexpr函数时才是正确

constexpr是在编译期确定的。
const不一定要在编译期确定。

constexpr和const数组的区别?
看完有些对constexpr更多的了解

字面值类型

算术类型、引用、指针都属于字面值类型
自定义的Sales_item、io库、string类型不属于字面值类型,也就不能定义为constexpr。

指针与constexpr

重点:在constexpr声明中如果定义一个指针,限定符constexpr仅对指针有效,对指针所指的对象无效

const int *p=nullptr; //p是一个指向整型常量的指针
constexpr int *q=nullptr;//q是一个指向整数的常量指针
constexpr int *q = nullptr;//q is a const pointer to int

p和q的类型相差很远,p是指向常量的指针,而q是一个常量指针,其中的关键在于constexpr把定义的对象变成了顶层const,

与其他常量指针类似,constexpr既可以指向常量也可以非常量

constexpr int *np=nullptr//np是一个指向整数的常量指针,其值为null
int j=0constexpr int i=42//i为整形常量
//i和j都定义在函数体外
constexpr const int *p=&i;//p是常量指针,指向zh

12. 处理类型

(1)类型别名
a. typedef
b.别名声明

    using SI=Sales_item;//SI是Sales_item的同义词

这种方法用关键字using做别名,其后紧跟别名和等号,其作用是把等于左侧的名字规定成等号右侧类型的别名.

类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名:

SI item; //等价于Sale_item item 

(2)auto类型说明符

编程时常常需要把表达式的值赋给变量, 这就要求在声明变量的时候清楚的知道表达式的类型, 有时候做不到, C++11新标准引入auto类型说明符,用它就能让编译器替我们分析表达式所属的类型, auto让编译器通过初始值来推算出变量的类型.显然,auto定义的变量必须有初始值.

auto item=val1+val2;//item初始化为val1和val2的和

使用auto也能在一条语句中声明多个变量,因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样

auto i=0, *p=&i; //正确,i是整数, p是整数指针
auto sz=0, pi=3.14; //错误, sz和pi类型不一致

复合类型, 常量和auto
(1)引用

int i=0, &r=i;
auto a=r; //a是一个整数

(2)auto一般会忽略掉顶层const, 同时底层const则保留下来

const int ci=i,&cr=ci;
auto b=ci;//b是一个整数

13. decltype类型

希望从表达式中的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量. c++11引入decltype, 作用是选择并返回操作数的数据类型, 在这个过程中, 编译器分析表达式, 但是不实际计算表达式的值

decltype (f()) sum=x; //sum的类型就是函数f的返回类型

decltype 处理顶层const和引用方式跟auto的方式有些不同. 如果decltype使用的表达式是一个变量, 则decltype返回的是变量的类型(包括顶层const和引用在内)

const int ci =0, &cj =ci;
decltype(ci) x=0; //x 的类型是const int;
decltype(cj) y=ci; //y 的类型是const int&, y绑定到变量x
decltype(cj) z;//错误,z是一个引用, 必须初始化

decltype 和引用
decltype的类型可以是引用.

int i=42, *p=&i, &r=i;
decltype(r+0) b; //正确,加法的结果是int, 因此b是一个未初始化的int
decltype(*p) c; //**错误, c是int& ,必须初始化**

因为r是一个引用, 因此decltype(r)的结果是引用类型. 如果想让结果类型是r所指的类型, 可以把r作为表达式的一部分,比如r+0,显然这个表达式结果将是一个具体值.

另一方面, 如果表达式的内容是解引用操作, 则decltype将得到引用类型,解引用指针可以得到指针所指的对象,而且还能对该对象赋值, 因此, decltype(*p)的结果类型是int&,而非int.

对于decltype: 如果变量名加上一堆括号, 则得到的类型与不加括号得到的不同:

如果decltype使用一个不加括号的变量得到就是该变量的类型;
如果变量加上一层或多层括号, 编译器就会把他当成一个表达式, 变量是一种作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型;

decltype((i))d;//错误;d是int&, 所以必须初始化
decltype(i)e; //正确;e是int
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2442次
    • 积分:183
    • 等级:
    • 排名:千里之外
    • 原创:13篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条
    文章分类