C++定义了两种 基本内置 类型 :算数类型(arithmetic type) 和 空类型 (void)
带符号类型 与 无符号类型(除布尔和拓展的字节型外)
带符号: int short long longlong(c++11)
对应无符号: unsigned unsigned short unsigned long unsigned longlong
在字符类型中 不同:
char (由编译器决定是否有符号) 注意:在算数表达中,不要使用char。仅存放字节或布尔值
有符号: signed char
无符号: unsigned char
字面值常量(literal): 每个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的类型。
例如: 十进制的字面值 的类型为: int、long、longlong中最小的
八进制的字面值 的类型为:int、unsigned int、long、unsigned long、longlong、unsigned longlong中最小的
浮点型字面值 的类型默认为:double
字符串字面值 的类型为:常量字符构成的数组(array)
布尔类型字面值 的类型为: true、false
指针类型字面值 的类型为:nullptr
另外,可以通过添加特定的前缀和后缀,改变整型、浮点型和字符型的字面值。
1E-3F //单精度浮点型字面值,类型是 float
42ULL //无符号整型字面值,类型是unsigned long long
cout << "如果两个字符串字面值位置近邻且" "仅有空格、缩进、换行符分割"
"实际上是一个整体"<<endl;
变量: 每个变量都具有数据类型,数据类型决定了占用的内存空间和布局方式。
对C++程序员来说,“变量(variable)”和“对象(object)”一般可以互换使用。
此书中,遵循大多数人习惯用法,即认为对象是具有某种数据类型的内存空间。使用时,不严格区分是类还是内置类型,也不区分时候命名或只读。
列表初始化
int units_sold = 0;
int units_sold = {0};
int units_sold(0);
int units_sold{0}; // C++11
在C++11中,{}初始化变量得到了全面应用。——列表初始化
默认初试化: 由 变量类型 和 定义变量的位置 决定了默认值
内置类型: 任何函数体之外 初始化为0
函数体内部 不被初始化(值未定义)
每个类自己确定初试化对象的方式,而且是否允许不经初始化就定义对象也有类确定。
注:建议初始化每个内置类型的变量(非必须)
声明&定义
C++支持分离式编译(separate compilation),允许将程序分割成若干个文件,每个文件单独编译。
同时出现一个问题:如何在文件间共享代码(如:一个文件的代码需要使用另一个文件中定义的变量)
故:将声明(declaration)和定义(definition)区分开
声明:使得名字为程序所知,一个文件想使用别处定义的名字则必须包含对那个名字的声明;
定义:创建与名字无关的实体;
extern int i; //声明
int j; //定义
注:1.任何包含了显示初始化的声明即成为定义。
2.变量的定义必须且只能出现在一个文件中,在其他用到其变量的文件必须对其声明。
函数声明(函数原型function prototype)
void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
函数的三要素(返回类型、函数名、形参类型(形参名字可省略))描述了函数的接口,说明了调用该函数所需的信息。
注:1.同样,函数也应该在头文件中声明而在源文件中定义。
2.定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义和声明是否匹配。
变量命名规范:
1.变量名一般用小写字母,如index;
2.用户自定义的类名一般用大写字母开头,如Sales_item;
3.如果由多个单词组成,则单词应有明显区分,如student_loan,studentLoan;
名字的作用域
——程序中,每一个名字都会指向一个特定的实体:变量、函数、类型。
#include <iostream>
int reused = 42;
int main()
{
int unique = 0;
std::cout << reused << "" << unique << std::endl; // 42 0
int reused = 0; //新建局部变量,覆盖全局
std::cout << reused << "" << unique << std::endl; // 0 0
std::cout << ::reused << "" << unique << std::endl; // 42 0
}
符合类型(compound type)——基于其他类型定义的类型(引用&指正)
一条声明语句: 基本数据类型 + 声明符(declarator)
声明符:1.命名一个变量 并 2.指定该变量 为与基本数据类型有关的 某种类型
指针和引用提出之前 声明符 == 变量名。
引用
int &refVal = ival; //refVal指向ival
因为引用本身不是一个对象,故不存在引用的(zai)引用
int a = 3;
int & (&b) = a; //error!
注意:引用的类型必需要与被绑定的对象严格匹配,有两种例外:
1.在初始化 常量引用时,允许用任意表达式作为初始值,只要该表达式的结果可以转化为引用的类型即可
int i = 3;
const int &r1 = i;
const int &r2 = 43;
const int &r3 = r1 * 2;
//原理:绑定了中间值
const int temp = 43;
const int &r2 = temp
2.在继承关系…………(待填坑)P534
指针
指针与引用的区别:1.指针本身是一个对象,允许赋值与拷贝
2.指针无须在定义时赋值
int a = 10;
int &(*b) = a; //error!
因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
注意:指针的类型必需要与被绑定的对象严格匹配,有两种例外:
1.在初始化 指向常量的指针(not!!!!常量指针)时,想要存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14; //pi是个常量,值不能改变
double *ptr = π //error!!! ptr是普通指针
const double *cptr = &pi //正确!!!
//例外情况:
double dval = 3.14; //双精度浮点数,值可以改变
cptr = &dval; //正确:但不能通过cptr改变dval的值
原理:和常量引用一样,所谓指向常量的指针仅仅要求不能通过指针改变对象的值,而没有规定那个值能不能通过其他方式改变。
2.待填坑……………………P534
空指针(Null pointer)
int *p1 = nullptr;
int *p2 = 0; //直接将p2初始化为字面值常量0
//注意#include cstdlib
建议所有的指针:1.尽量等定义了对象之后在定义指向它的指针。
2.不知道指针应该指向何处,就初始化为 nullptr (C++11)
void* 指针 ——可以存放任意对象的地址
可以使用的地方:1.拿它和别的指针比较
2.作为函数的输入或输出
3.赋给另外一个void*指针
注:因为我们不知道对象究竟是什么类型,故不能直接操作指针所指的对象
double obj = 3.14, *pd = &obj;
void *pv = &obj;
pv = pd;
变量的定义包括一个基本数据类型(base type)和一组声明符【见前述】
int i = 10, *p = &i, &r = i; //基本数据类型相同,但声明符不同
指向指针的引用:
int i = 42;
int *p;
int *&r = p; //r是一个对指针p的引用
r = &i; //令p指针指向i
*r = 0; //将i的值赋0
面对一条比较复杂的指针或引用的声明语句(*&r)时,从右向左阅读有助于弄清楚它的真实含义。
离变量名最近的符号(&)对变量的类型有最直接的影响,因此r是一个引用。声明符的剩余部分用以确定r引用的类型是什么,符号*说明r引用的是一个指针。最后声明的基本数据类型部分指出r引用的是一个int型的指针。
const限定符
const int bufSize = 512;
因为const对象一旦创建后其值就不能改变,所以const对象必须初始化。
默认状态下,const对象仅在文件内有效,当多个文件中出现同名const变量,等同于在不同文件中分别定义了独立的变量。
当我们不希望编译器为每个文件分别生成独立的变量:
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
//file_1.cc中定义
extern const int bufSize = fun();
//file_1.h头文件
extern const int bufSize;
const的引用
把引用绑定到const对象(=左边),就像绑定到其他对象上一样,我们称之为对常量的引用(简称:“常量引用” reference to const)
int i = 42;
int &r1 = i; //引用r1绑定对象i
const int &r2 = i; //r2也绑定对象i
r1 = 0; //i=0
r2 = 0; //error!! r2是常量引用
划重点:常量引用仅对引用的可参与的操作做出了限定,对于引用的对象本身(赋值右侧)是不是一个常量未做限定。
const和指针
指向常量的指针(pointer to const)(not!!!常量指针):不能用于改变所指对象的值。
const double pi = 3.14;
const double *cptr = π //正确 指向一个双精度常量
double dval = 3.14;
const double *cptr = &dval; //正确 但是不能通过cptr改变dval
常量指针(const pointer):不能改变存放在指针中的地址。
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = &pip; //pip一个指向常量对象的常量指针
curErr指向的是一个非常量的一般整数,完全可以用curErr去修改errNumb的值;
pip是一个指向常量的常量指针,地址以内容都不能更改;
用 顶层const(top-level const) 表示指针本身是个常量
用 底层const(low-level const) 表示指针所指的对象是个常量
更一般的:
顶层const:表示任意的对象是常量,对任何数据类型都适用,如算数类型、类、指针。
底层const:与指针和引用等复合类型的基本类型部分有关。
int i = 0;
int *const p1 = &i; //顶层const
const int ci = 42; //顶层const
const int *p2 = &ci; //底层const
const int * const p3 = p2 //底层const 顶层const
const int &r = ci; //底层const
在执行对象的拷贝操作时,顶层const和底层const区别明显:
顶层const:无影响。
底层const:拷入拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。
p2 = p3; //都具有底层const
p2 = &i; //int*能转换成const int*
constexpr和常量表达式
常量表达式(const expression)是指①值不会改变②在编译过程就能得到计算结果的表达式。(之前提及的字面值、用常量表达式初始化的const对象也可以是常量表达式)
一个对象(或表达式)是不是常量表达式由 数据类型 和 初始值 共同决定
const int max_files = 20;
const int limit = max_files + 1;
int staff_size = 27; //error!!!
const int sz = get_size(); //error!!!
C++11中,允许将变量声明为constexpr类型以便由编译器验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
constexpr int mf = 19;
constexpr int limit = mf + 1;
constexpr int sz = size(); //只有当size是一个constexpr函数时才是正确的声明
故:如果认为变量是一个常量表达式,那就把它声明成constexpr类型
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。
字面值类型(literal type): 类型一般比较简单、值也显而易见、容易得到。包括:算术类型、引用和指针。
尽管指针和引用可以定义成constexpr,但它们的初始值却受到严格的限制。一个constexpr指针的初始值必须为nullptr。
类型别名(type aliaa):一个名字,它是某种类型的同义词。
typedef double wages; //wages是double的同义词
typedef wages base , *p; //base是double的同义词,p是double*的同义词
声明语句: typedef double wages 其中,基本数据类型【typedef double】, 定义的类型别名【wages】;
using SI = Sales_item;
C++11新标准,使用别名声明(alias declaration)定义类型的别名。
难点剖析:
typedef char* pstring;
const pstring cstr = 0; //常量指针
//如改写理解:指向常量字符的指针!错误!!
const char *cstr = 0; //错误!!!基本数据类型是char
原声明语句的基本数据类型为【const pstring】。其中,【const】给定类型的修饰,【pstring】指向char的指针。故原始语句为指向char的常量指针。
auto类型说明符
C++11,auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。
//auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值
auto item = val1 + val2;
注:1.对引用对象,编译器以引用对象的类型作为auto的类型
int i = 0, &r = i;
auto a = r; //a为int型
2.auto一般会忽略顶层const,保留底层const。
const int ci = i, &cr = ci;
auto b = ci; //int
auto c = cr; //int
auto d = &i; //int *
aute e = &ci; //指向整型常量的指针
decltype类型说明符
C++11,作用:选择并返回操作数的数据类型(编译器分析表达式并得到它的类型,却不计算表达式的值)
decltype(f()) sum = x; //sum的类型为函数f的返回类型
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // const int
decltype(cj) y = x; // const int&
decltype使用的表达式是一个变量,则返回该变量的类型(包括顶层const和引用)
struct Sales_data{
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0
};
类右侧的表示结束的花括号后必须写一个分号,因为类体后面可以直接跟变量名以示对该类型对象的定义。
struct Sales_data{
/*……………………*/
}accum, trans, *salesprt;
但不建议上述写法,将类的定义和对象的定义相混合。
预处理器概述
确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor)
#ifndef SALES_DATA_H //判断SALES_DATA_H是否定义
#define SALES_DATA_H //将SALES_DATA_H设置为预处理变量
#include <string>
struct Sales_data{
std:: string bookNo;
unsigned units_sold = 0;
};
#endif
预处理器是在编译之前执行一段程序,可以部分的改变我们所写的程序。
如预处理功能#include,当预处理器看到#include标记时会用指定的头文件的内容代替#include。
注:整个程序中预处理变量包括头文件保护符必须唯一,通常基于头文件中类的名字构建保护符的名字,并将预处理变量全部大写。
术语表:
常量指针:一种指针,它的值永远不会变;
常量引用:一种习惯叫法,含义是指向常量的引用;
声明:声称存在一个变量、函数或者是别处定义的类型。名字必须在定义或者声明之后才能使用。
定义:为某一特定类型的变量申请存储空间,可以选择初始化该变量。
头文件保护符:使用预处理变量防止头文件被某个文件重复包含。
初始化列表:利用花括号把一个或多个初始值放在一起初始化的形式。
类型检查:是一个过程,编译器检查程序使用某给定类型对象的方式与该类型的定义是否一致。