c++ primer 学习笔记(一)

2.1 基本内置类型

2.1.1算数类型

short 16位 long 32位 long long 64位(c++11新定义)

wchar_t 用于确保可以存放机器最大扩展字符集中任意一个字符

char16_t char32_t 为Unicode服务

除bool型和扩展的字符型以外,其他整形可以划分为带符号的和无符号的

字符型被分为 char/signed char/unsigned char,尽管字符型有三种,但是字符表现形式只有两种 signed 和 unsigned ,char会表现为上述两种中的一种,由编译器决定

在算数表达式中不要使用char或bool,如果一定要使用char ,指明 signed or unsigned

执行浮点数运算用double: float 精度通常不够,double 与 float 运行时间相差无几,但是long double的运行时间不容忽视

2.1.2 类型转换

非bool 转为bool 0->true 非0->false

bool转为非bool false->0 true->1

浮点转整数仅保留浮点数小数点之前部分

整数转浮点,小数部分记为0,如果整数所占空间超过浮点类型容量,可能损失精度

赋给无符号类型一个超出其范围的值,结果是初始值对该类型表示数值总数取模后的余数

赋给给带符号类型一个超出其范围的值,结果是未定义的

切勿混用带符号类型和无符号类型,因为带符号数会自动转化为无符号数

2.1.3 字面值常量

一个形如42的值被称作字面值常量,每个字面值常量对应一个数据类型,字面值常量的形式和值决定了它的数据类型

整形和浮点型:

以0开头表示8进制数,以0x or OX开头代表16进制

eg 20=024=0x14

十进制字面值是带符号数,是int/long/long long中能容纳的最小的一个

8进制 16进制是 int / unsigned int / long / unsigned long / long long / unsigned long long 中最小的

如果一个字面值连与之关联的最大数据类型都放不下,会引发错误

short没有对应的字面值

严格的讲,十进制字面值不是负数,符号的作用是对字面值取负值

浮点型表示为一个小数或科学计数法表示的指数,指数部分用e或E

eg: 3.14 3.14E0 0. 0e0 .001

默认为double

字符和字符串

编译器在每个字符串结尾添加一个'/0',因此字符串字面值实际长度比内容多1

书写的字符串字面值比较长,可以分开书写

std::cout <<"a really, really long string interal"
            "that spans two lines" << std::endl;
转义序列

换行符 \n 横向制表符  \t 报警符 \a 纵向制表符 \v 退格符 \b 双引号 \" 反斜线 \\ 问号 \? 单引号 \' 回车符 \r 进纸符\f

指定字面值的类型

L'a'//宽字符型字面值,类型 wchar_t
u8"hi!"//utf-8字符串字面值
42ULL//无符号整形字面值,unsigned long long
1E-3F//单精度浮点字面值 float
3.14159L//long double
长整形字面值用大写L 小写l和1易混

字符和字符串字面值

前缀  含义             类型

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

布尔字面值和指针字面值

true/false 是bool类型字面值

nullptr 是指针字面值

2.2 变量

2.2.1变量定义

变量初始化

初始化不等于赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值替代

定义一个名为sold的int变量并初始化为0:

int sold=0;
int sold={0};
int sold{0};
int sold(0);
c++11标准用花括号来初始化变量得到全面应用,这种初始化方法叫列表初始化

如果使用列表初始化且初始值存在丢失信息的风险:

long double ld=3.14;
int a{ld},b={ld}; //错误,转换未执行,因为存在丢失信息风险
int c(ld),d=ld; //正确,转换执行,且确实丢失了部分值
3.2.1 3.3.1对列表初始化有更多介绍

默认初始化

定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量不被初始化,一个为被初始化的内置类型变量的值是未被定义的

每个类各自决定其初始化对象的方式,而且是否允许不经初始化就定义对象也由类自己决定(如不允许,如果创建但未初始化则引发错误)

绝大多数类都支持无需显示初始化而定义对象,如string生成一个空串

2.2.2 变量声明和定义的关系

分离式编译:允许将程序分割成若干个文件,每个文件可被独立编译

为了支持分离式编译,c++将声明和定义区分开,声明(declaration)是的名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明,而定义(definition)负责创建于名字关联的实体

变量声明规定了变量的类型和名字,定义还申请储存空间,也可能为变量赋一个初始值

如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式初始化变量

extern int i; //声明i而非定义i
int j; //声明并定义j
任何包含了显式初始化的声明即成为定义。

extern double pi=3.14; //定义
在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误

变量只能被定义一次,但可以被多次声明

2.2.3 标识符

C++用户自定义标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头,定义在函数体外的标识符不能以下划线开头

变量命名规范

变量名一般用小写字母,类名一般以大写字母开头,如果标识符由多个单词组成,应有明显区分

2.2.4 名字的作用域

名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束

全局作用域:定义于所有花括号之外,在整个程序范围内都可使用

块作用域:一部分可以使用

被嵌套的作用域称为内层作用域,嵌套别的作用域的称为外层作用域

允许内层作用域重新定义外层作用域已有名字,但不宜定义

#include
int reused=1;
int main()
{
	std::cout << reused << std::endl; // 1
	int reused=0;
	std::cout << reused << std::endl; //0
	std::cout << ::reused<< std::endl; //1 显式访问全区变量reused
	return 0;
}

2.3 复合类型

2.3.1 引用

c++11增添右值引用(13.6.1),主要用于内置类,在使用术语"引用(reference)"时,实际指左值引用

引用为对象起了另外一个名字,将声明符写成&d来定义引用类型

int ival=1024;
int &refVal=ival; //refVal指向ival(是ival的另一个名字)
int &refVal2;//错误:引用必须初始化
引用并非对象,只是为已经存在的对象所起的另一个名字,定义了一个引用后,对其进行的所有操作都是在与之绑定的对象上进行的。

因为引用并非对象,所以不能定义引用的引用

允许一条语句定义多条引用,每个引用标识符必须以&开头

除了2.4.1与15.2.3介绍的两种例外情况,其他所有引用的类型必须和与之绑定的对象严格匹配,而且引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起。

2.3.2 指针

指针(pointer)是只想另一种类型的复合类型,与引用相似而又不同(1)指针本身是对象,允许对指针赋值和拷贝,也可以指向不同对象(2)无需定义时赋初值

获取对象的地址

int val=42;
int *p=&val;//p存放的是ival的地址,或者说p是指向变量ival的指针
因为引用不是对象,不能定义指向引用的指针,除2.4.2 15.2.3两种特殊情况,类型要严格匹配

指针值

指针的值(即地址)应属于下列4种情况之一
1 指向一个对象 2 指向紧邻对象所占空间的下一个位置 3 空指针,即为指向任何对象 4 无效指针(除上述情况的所有情况)
试图拷贝或以其他方式访问无效指针的值将引发错误,编译器不负责检查
第二第三种形式没有指向任何对象,所以试图访问的行为也不被允许

利用指针访问对象

用解引用符(*)来访问对象
int val=42;
int *p=&val;
cout<<*p; //输出42
对指针解引用会得出所指的对象,如果给解引用结果赋值,就相当于给指针所指对象赋值
*p=0;
cout << *p //输出0

解引用操作仅适用于确实指向某个对象的有效指针

空指针

int *p1=nullptr;//c++11标准
int *p2=0;//需include
int *p3=NULL;

NULL属于预处理变量 预处理起是运行于编译过程之前的一段程序,当用到一个预处理变量,预处理器会自动替换为实际值
建议初始化所有指针

赋值和指针

赋值永远改变的是等号左侧的对象
int i=42;
int *p1=&i; //pi被初始化,存有i的地址
int *p2=nullptr;
p2=&i; //p2指向i
*p2=1; //i的值变成1,p2未改变

其他指针操作

只要有一个合法值,就能用在条件表达式,如果指针的值是0,条件取false,否则取true
两个类型相同的合法指针,可以用"==" "!="进行比较,比较结果为bool类型,如果地址相同,它们相等,反之不相等
地址值相同三种可能:都为空,都指向同一个对象,都指向同一个对象的下一个地址
一个指针指向某对象,另一个指针指向另一个对象的下一个地址,它们可能相等
3.5.3将介绍更多指针操作

void*指针

void*指针存放一个地址,但我们并不了解到底是个什么类型的对象
void*可以做的事:拿它和别的指针比较,作为函数的输入输出,赋给另一个void*指针
不能直接操作void*所指对象,因为不知道具体类型
19.1.1 4.11.3有其他介绍

2.3.3 理解复合类型的声明

指向指针的指针

int ival=1024;
int *pi=&ival; //pi指向一个int 型的数
int **ppi=π//ppi指向一个int型指针

为了访问最原始的对象,需要对指针的指针做两次解引用(**ppi)

指向指针的引用

int *&r=p;
理解r的类型,从右向左阅读r的定义,离变量名最近的有最直接的影响,因此r是个引用

2.4 const限定符

const常量一旦创建后其值就不能再改变,所以const常量必须初始化,初始化可以是任意复杂表达式

默认状态下,const对象仅在文件内有效,多个文件中出现同名const变量,等同于在不同文件中分别定义了独立的变量

如果不是常量表达式,并需要在文件间共享,即在一个文件中定义const,而在其他多个文件中声明并使用:

不管是声明还是定义都添加extern关键字,定义中extern表示可被其他文件使用,声明中extern表示它的定义将在别处出现

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

2.4.1 const的引用

const int ci=1024;
const int &r1=ci;
r1=42;//错误:r1是对常量引用,不能修改
int &r2=ci;//错误:试图让一个非常量引用指向一个常量对象

常量引用是对const的引用

引用的类型必须和所引用的对象一致,例外情况 初始化常量引用时时可以用任意表达式作为初始值,只要该表达式结果能转换成引用的类型,允许一个常量引用绑定非常量的对象。

常量引用的的实质:

double dval=3.14;
const int &r1=dval;
//等价于
const int temp=dval;
const int &ri=temp;

ri绑定了一个临时量,临时量对象是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名对象。

但是初始化非常量引用时不允许如此。

对const的引用可以引用一个非const对象

2.4.2 指针和const

const double pi=3.14; //pi是一个常量
double *ptr=π //错误,不能用非常量指针指向常量
const double *cptr=π //正确
*cptr=42; //错误,不能给cptr赋值
double dval=3.14;//dval是个双精度浮点数
cptr=&dval;//正确,但是不能通过cptr改变dval的值

可以改变指向常量的指针指向的地址,但是不能通过这个指针改变所指向常量的值

const指针

允许把指针本身定为常量,常量指针必须初始化,一旦初始化完成,存放的那个地址就不能改变,但是指向的对象的值可以改变
把*放在const关键字之前说明指针是一个常量
int e=0;
int *const cur=&e;//cur将一直指向e
const double pi=3.14;
const double *const pip=π //pip是一个指向常量对象的常量指针

2.4.3 顶层const

指针是不是常量和指针所指的对象是不是常量是两个独立的问题,名词 顶层const指指针本身是一个常量, 底层const指指针所指的对象是一个常量
顶层const可以表示任意的对象是常量,底层const则与指针和引用等复合类型的基本类型部分有关,指针既可以是顶层const也可以是底层const,这一点和其他类型区别明显

2.4.4 constexpr和常量表达式

常量表达式指值不会改变且在编译过程中就能得到计算结果表达式。字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式
一个对象是不是常量表达式由数据类型和初始值共同决定
const int max_files=20; //max_files是常量表达式
const int limit=max_files+1;//limit是常量表达式
int staff_size=27; //staff_size不是常量表达式,数据类型是int
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函数时是一个正确声明语句
6.5.2介绍允许定义constexpr函数

字面值类型

constexpr指针的初始值必须是nullptr或0,或储存于某个固定地址中的对象
6.1.1提到,函数体内定义的变量一般并非存放在固定地址中,constexpr指针不能指向这样的变量,定义于函数体之外的地址固定不变,可以初始化constexpr指针
6.1.1提到,允许函数定义一类有效范围超出函数本身的变量,就有了固定地址,可以初始化constexpr指针

指针和constexpr

在constexpr声明中定义了一个指针,constexpr仅对指针有效,与指针所指对象无关
constexpr把它所定义的对象置为顶层const
constexpr int *np = nullptr; // np是一个指向整数的常量指针,其值为空
int j = 0;
constexpr int i = 42; //i是整形常量
constexpr const int *p = &i; //p是常量指针,指向整形常量
constexpr int *p1 = &j; //p1是常量指针,指向j

2.5 处理类型

2.5.1 类型别名

typedef double wages; //wages是double的同义词
typedef wages base , *p //base是double的同义词,p是double*的同义词
using SI Sales_item; // SI是Sales_item 的同义词
类型别名和类型的名字等价

指针常量和类型别名

typedef char *pstring;
const pstring cstr = 0; //cstr是指向char的常量指针
const pstring *ps; //ps是一个指针,他的对象是指向char的常量指针
上述两条声明语句的基本数据类型都是const pstring,和过去一样,const是对给定类型的修饰。 pstring实际上是指向char的指针,因此,const pstring是指向char的常量指针
而非指向常量字符的指针
遇到了一条使用了类型别名的声明语句,人民往往会 错误地尝试把类型别名替换成它本来的样子
const char *cstr = 0; //错误理解
声明语句中用到pstring,其基本数据类型是指针,可是用char*重写了声明语句后,数据类型就变成了char ,*成了声明符的一部分,这样改写的结果是,const char成了基本数据类型。

2.5.2 auto类型说明符

c++11标准引入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

编译器会适当改变结果类型使其更符合初始化规则
编译器以引用对象的类型作为auto的类型
int i = 0, &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; //f是const int

将引用的类型设为auto:
auto &g = ci; //g是一个整形常量引用,绑定到ci
auto &h = 42; //错误 不能为非常量引用绑定字面值
const auto &j = 42; //正确,可以为常量引用绑定字面值

2.5.3 decltype类型指示符

希望从表达式的类型推断出要定义的变量的类型,但是不希望用该表达式的值初始化变量
decltype选择并返回操作数的数据类型
decltype(f()) sum = x; // sum的类型是函数f的返回类型
编译器并不实际调用函数f
decltype处理顶层const和引用的方式与auto有些许不用
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //x的类型是 const int
decltype(cj) y = x; //y的类型是 const int&,y绑定到变量x
decltype(cj) z; //错误 z是一个引用,必须初始化
引用从来都作为其所指对象的同义词出现,只用用在decltype处是个例外

decltype和引用

如果decltype使用的表达式不是一个变量,decltype返回表达式结果对应的类型 4.1.1:有些表达式将向decltype返回一个引用类型,一般来说这意味着该表达式的结果对象能作为一条赋值语句的左值
//decltype的结果可以是引用类型
int i = 42, *p = &i, &r = i;
decltype(r+0) b; //正确,b是一个未初始化的int
decltype(*p) c; //错误,c是int&,必须初始化
因为r是一个引用,decltype(r)是引用,让结果类型是r所指的类型,可以把r作为表达式的一部分,如r+0
如果表达式的内容是解引用操作,则可以得到引用类型,解引用指针可以得到指针所指的对象,还能给这个对象赋值,因此,decltype(*p)的结果类型是int&,而非int
如果decltype使用的是一个不加括号的变量,得到的结果是该变量的类型,如果给变量加上了一层或多层括号,编译器会把它当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型
decltype((i)) d; //错误:d是引用,必须初始化
decltype(i) e; //正确,e是一个int

2.6 自定义数据结构

c++11规定,可以为数据成员函数提供一个类内初始值,没有初始值的成员将被默认初始化
struct Sales_data
{
    std::string boolNol
    unsigned units_sold = 0;
    double revenue = 0.0;
};

编写自己的头文件

类一般都不定义在函数体内,在不同文件中使用同一个类,类的定义必须保持一致
类通常被定义在头文件中,而且类所在头文件名字应与类的名字一样
头文件通常包含只能被定义一次的实体,如类,const,constexpr变量
有必要在书写头文件时做适当处理,使其遇到多次包含的情况能安全正常工作
头文件一旦改变,相关源文件必须重新编译以获得更新过的声明

预处理器概述

确保头文件多次包含仍能安全工作的常用技术是预处理器。预处理器是在编译之前执行的一段程序,可以部分改变我们所写的程序,例如之前用过#include<>
头文件保护符 头文件保护符依赖于预处理变量,预处理变量有两种状态 已定义和未定义 #define指令把一个名字设定为预处理变量 #ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真,一旦检查结果为真,执行后续操作直到#endif
防止重复包含:
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include
struct Sales_data
{
    std::String boolNo;
};
#endif
预处理变量无视c++语言中关于作用域的规则
一般把预处理变量的名字全部大写








你可能感兴趣的:(c++ primer 学习笔记(一))