C++ primer 1-2章 变量和基本类型
第一章 开始
一些背景:
在C++ 11之前,C++存在最严重的缺陷就是缺少自动内存管理和对象级别的消息发送机制。
MFC:Microsoft Foundation Classes 微软基础类库
C++可以看作是由三部分组成的:低级语言,现代高级语言特征,标准库函数。
编译或者运行程序可以用IDE或者命令行来实现。
C++最常见的后缀包括.cc,.cxx,.cpp,.cp,.c
熟悉编译器:
g++:
- 编译:
g++ --std=c++11 ch01.cpp -o main
- 运行:
./prog1
- 查看运行状态:
echo $?
- 编译多个文件:
g++ ch2.cpp Sales_item.cc -o main
输入 g++ --help
,查看编译器选项:
输入 g++ -v --help
可以看到更完整的指令。 例如还有些常用的:
获得程序状态:
- windows:
echo %ERRORLEVEL%
- UNIX:
echo $?
IO
#include <iostream>
std::cout << "hello"
std::cin >> v1
记住>>
和<<
返回的结果都是左操作数,也就是输入流和输出流本身。
标准库处理cin和cout还定义了两个ostream对象:cerr 和 clog。通常用cerr输出警告和错误消息,它也被称为标准错误,用clog输出程序运行时的一般性信息。
endl:这是一个被称为操纵符(manipulator)的特殊值,效果是结束当前行,并将设备关联的缓冲区(buffer)中的内容刷到设备中。
头文件:类的类型一般存储在头文件中,标准库的头文件使用<>
,非标准库的头文件使用""
。申明写在.h
文件,定义实现写在.cpp
文件。
避免多次包含同一头文件:
1 |
|
成员函数(类方法):使用.
调用。
命名空间(namespace):使用作用域运算符::
调用。
注释
单行注释:
//
多行注释:
/* */
。编译器将/*
和*/
之间的内容都作为注释内容忽略。注意不能嵌套。UNIX和Mac下键盘输入文件结束符:
ctrl+d
,Windows下:ctrl+z
while语句
循环执行,(直到条件(condition)为假。
for语句
循环头由三部分组成:
- 一个初始化语句(init-statement)
- 一个循环条件(condition)
- 一个表达式(expression)
一般情况下,循环次数已知就选择for,循环次数未知就选择while;
1 | while (std::cin >> value) //读取输入数量不定的数据 |
使用文件重定向:
1 | addItems <infile> outfile |
意思就是有一个编译好的addItems.exe文件,上面的一条命令指的是从infile文件中读入销售记录,并将输出结果写入到outfile文件中。
第二章 变量和基本类型
基本内置类型
基本算数类型:
类型 | 含义 | 最小尺寸 |
---|---|---|
bool |
布尔类型 | 8bits |
char |
字符 | 8bits |
wchar_t |
宽字符 | 16bits |
char16_t |
Unicode字符 | 16bits |
char32_t |
Unicode字符 | 32bits |
short |
短整型 | 16bits |
int |
整型 | 16bits (在32位机器中是32bits) |
long |
长整型 | 32bits |
long long |
长整型 | 64bits (是在C++11中新定义的) |
float |
单精度浮点数 | 6位有效数字 |
double |
双精度浮点数 | 10位有效数字 |
long double |
扩展精度浮点数 | 10位有效数字 |
如何选择类型
1.当明确知晓数值不可能是负数时,选用无符号类型;当由于无符号数和int类型数操作使得无符号类型被赋予负值时其实际值是2n + 这个负值,n取决于机器的int类型占多少位。
2.使用
int
执行整数运算。一般long
的大小和int
一样,而short
常常显得太小。除非超过了int
的范围,选择long long
。3.算术表达式中不要使用
char
或bool
。4.浮点运算选用
double
。
类型转换
- 非布尔型赋给布尔型,初始值为0则结果为false,否则为true。
- 布尔型赋给非布尔型,初始值为false结果为0,初始值为true结果为1。
字面值常量
20 :十进制 024:八进制 0x14:十六进制 INT_MAX,INT_MIN等
‘a’ :字符字面值 “Hello World”:字符串字面值
分多行书写字符串。
1
2std:cout<<"wow, a really, really long string"
"literal that spans two lines" <<std::endl;转义序列。
\n
、\t
等。- 布尔字面值。
true
,false
。 - 指针字面值。
nullptr
字符串型实际上时常量字符构成的数组,结尾处以'\0'
结束,所以字符串类型实际上长度比内容多1。
变量
变量提供一个具名的、可供程序操作的存储空间。 C++
中变量和对象一般可以互换使用。
变量定义
定义形式:类型说明符(type specifier) + 一个或多个变量名组成的列表。如
int sum = 0, value, units_sold = 0;
初始化
(initialize):对象在创建时获得了一个特定的值。
- 初始化不是赋值!:
- 初始化 = 创建变量 + 赋予初始值
- 赋值 = 擦除对象的当前值 + 用新值代替
- 列表初始化:使用花括号
{}
,如int units_sold{0};
- 默认初始化:定义时没有指定初始值会被默认初始化;在函数体内部的内置类型变量将不会被初始化;这个初始化需要人为在函数中写。
- 建议初始化每一个内置类型的变量。
举例说明:
1 | std::string global_str; |
global_str
和global_int
是全局变量,所以初值分别为空字符串和0。 local_int
是局部变量并且没有初始化,它的初值是未定义的。 local_str
是 string
类的对象,它的值由类确定,为空字符串。
变量的声明(declaration) vs 定义(define)
- 为了支持分离式编译,
C++
将声明和定义区分开。声明使得名字为程序所知。定义负责创建与名字关联的实体。 - extern:只是说明变量定义在其他地方。
- 只声明而不定义: 在变量名前添加关键字
extern
,如extern int i;
。但如果包含了初始值,就变成了定义:extern double pi = 3.14;
- 声明并定义:
double pi
;
- 只声明而不定义: 在变量名前添加关键字
- 变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。因此如果多个文件需要使用同一个变量,就必须将声明和变量分离。
- extern int ix = 1024; //定义
- int iy; //定义
- extern int iz; //声明
- 名字的作用域(name scope){}
- 推荐:第一次使用变量时再定义它。
- 嵌套的作用域
- 同时存在同名的全局和局部变量时,已定义局部变量的作用域中可用
::reused
显式访问全局变量reused。 - 但是用到全局变量时,尽量不适用重名的局部变量。
- 同时存在同名的全局和局部变量时,已定义局部变量的作用域中可用
变量命名规范
- 需体现实际意义
- 变量名用小写字母
- 自定义类名用大写字母开头:Sales_item
- 标识符由多个单词组成,中间须有明确区分:student_loan或studentLoan,不要用studentloan。
左值和右值
- 左值(l-value)可以出现在赋值语句的左边或者右边,比如变量;
- 右值(r-value)只能出现在赋值语句的右边,比如常量。
复合类型
引用
一般说的引用是指的左值引用
- 引用:引用是一个对象的别名,引用类型引用(refer to)另外一种类型。如
int &refVal = ival;
//refVal是ival的另一个名字。 - 引用必须初始化。int &refVal是不合法的,必须直接对其赋值。
- 引用和它的初始值是绑定bind在一起的,而不是拷贝。一旦定义就不能更改绑定为其他的对象。
- 引用并非对象,它只是已经存在的对象的另一个名字。
- 引用必须绑定在对象上,不能绑定在一个字面值常量上。
函数中引用传参和变量传参的区别:
引用传参在自定义的函数中改变,在主函数中也改变;变量传参在自定义的函数中改变,在主函数中不改变。
指针
int p; //*指向int型对象的指针
是一种
"指向(point to)"
另外一种类型的复合类型。定义指针类型:
int *ip1;
,从右向左读有助于阅读,ip1
是指向int
类型的指针。指针存放某个对象的地址。
获取对象的地址:
int i=42; int *p = &i;
。&
是取地址符,p存放int变量ival的地址,或者说p是指向ival的指针;如果是int *p = i
,p是指向int类型的的指针。指针的类型与所指向的对象类型必须一致(均为同一类型int、double等)
指针的值的四种状态:
1.指向一个对象;
2.指向紧邻对象的下一个位置;
3.空指针,指针不指向任何对象;
4.无效指针,除1,2,3情况的其他值。
对无效指针的操作均会引发错误,第二种和第三种虽为有效的,但理论上是不被允许的
指针访问对象:
cout << *p;
输出p指针所指对象的数据,*
是解引用符。- 空指针不指向任何对象。使用
int *p=nullptr(NULL);
来使用空指针。 - 指针和引用的区别:引用是另一个对象的别名,而指针本身就是一个对象。 引用必须初始化,并且一旦定义了引用就无法再绑定到其他对象。而指针无须在定义时赋初值,也可以重新赋值让其指向其他对象。
赋值语句永远改变的是左侧的对象。
pi = &ival
//pi的值被改变,现在指向了ival的地址*pi = 0
//pi的值没有改变,ival的值改变
void*
指针可以存放任意对象的地址。因无类型,仅操作内存空间,对所存对象无法访问,无法确定能对对象做哪些操作。- 其他指针类型必须要与所指对象严格匹配。
- 两个指针相减的类型是
ptrdiff_t
,减法运算的值为两个指针在内存中的距离(以数组元素的长度为单位,而非字节),也就是距离为减法运算的结果将除以数组元素类型的长度。 - 建议:初始化所有指针。
int* p1, p2;//*是对p1的修饰,所以p2还是int型
- 存在对指针的引用,但不存在指向引用的指针。
const限定符
- 动机:希望定义一些不能被改变值的变量。
初始化和const:
- const对象必须初始化,且不能被改变。
- const变量默认不能被其他文件访问,非要访问,必须在指定const定义之前加extern。要想在多个文件中使用const变量共享,定义和声明都加const关键字即可。
const的引用:
reference to const(对常量的引用):指向const对象的引用,如
const int ival=1; const int &refVal = ival;
可以读取但不能修改refVal
。临时量(temporary)对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建的一个未命名的对象。
1
2
3
4
5
6
7double dval = 3.14;
const int &ri = dval;
//编译器执行时会变成:
double dval = 3.14;
const int temp = dval;
const int &ri = temp;
//temp就是一个临时量变量对临时量的引用是非法行为。
指针和const
- pointer to const(指向常量的指针):不能用于改变其所指对象的值, 如
const double pi = 3.14; const double *cptr = π
。 - const pointer:指针本身是常量,也就是说指针固定指向该对象,(存放在指针中的地址不变,地址所对应的那个对象值可以修改)如
int i = 0; int *const ptr = &i;
普通指针可以赋给常量指针,但是反过来不行。
顶层const
顶层const
:指针本身是个常量。*const1
2
3int num_b = 2;
int *const p_b = &num_b; //顶层const
//p_b = &num_a; //错误,常量指针不能改变存储的地址值底层const
:指针指向的对象是个常量。拷贝时严格要求相同的底层const资格。const*1
2
3int num_a = 1;
int const *p_a = &num_a; //底层const
//*p_a = 2; //错误,指向“常量”的指针不能改变所指的对象
constexpr
和常量表达式
- 常量表达式:指值不会改变,且在编译过程中就能得到计算结果的表达式。
C++11
新标准规定,允许将变量声明为constexpr
类型以便由编译器来验证变量的值是否是一个常量的表达式。- constexpr只对指针有效,对指针所指的对象无效。
1 | const int *p = nullptr; //p是一个指向整型常量的指针 |
const和constexpr的区别
C++11/14 constexpr 用法 - 简书 (jianshu.com)
其实,const并不能代表“常量”,它仅仅是对变量的一个修饰,告诉编译器这个变量只能被初始化,且不能被直接修改(实际上可以通过堆栈溢出等方式修改)。而这个变量的值,可以在运行时也可以在编译时指定。
constexpr可以用来修饰变量、函数、构造函数。一旦以上任何元素被constexpr修饰,那么等于说是告诉编译器 “请大胆地将我看成编译时就能得出常量值的表达式去优化我”。
处理类型
类型别名
- 传统别名:使用typedef来定义类型的同义词。
typedef double wages;
//wages是double的别名 - 新标准别名:别名声明(alias declaration):
using SI = Sales_item;
//SI是Sales_item的别名(C++11)
1 | // 对于复合类型(指针等)不能代回原式来进行理解 |
auto类型说明符 c++11
- auto类型说明符:让编译器自动推断类型,因为声明变量并用表达式赋值变量时很有可能不知道表达式的类型是什么。
- 一条声明语句只能有一个数据类型,所以一个auto声明多个变量时只能相同的变量类型(包括复杂类型&和*)。
auto sz = 0, pi =3.14//错误
int i = 0, &r = i; auto a = r;
推断a
的类型是int
。- 会忽略
顶层const
,如果希望推断出的auto类型是一个顶层const,需要写为const auto f = ci
的形式。 const int ci = 1; const auto f = ci;
推断类型是int
,如果希望是顶层const需要自己加const
decltype类型指示符(C++ 11)
- 从表达式的类型推断出要定义的变量的类型。
- decltype:选择并返回操作数的数据类型,编译器分析表达式并得到它的类型,但是不实际计算表达式的值。
decltype(f()) sum = x;
推断sum
的类型是函数f
的返回类型。- 不会忽略
顶层const
。 - 如果对变量加括号,编译器会将其认为是一个表达式,如int i—>(i),则decltype((i)) d得到结果为int&引用,此时必须初始化。如果是decltype(i) e,就会认为e是一个未初始化的int。双层括号的结果永远是引用,单层括号只有括号里是引用时才是引用。
- 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。decltype(a = b) d = a; 就是int&类型。
struct
尽量不要吧类定义和对象定义放在一起。如
struct Student{} xiaoming,xiaofang;
- 类可以以关键字
struct
开始,紧跟类名和类体。 - 类数据成员:类体定义类的成员。
C++11
:可以为类数据成员提供一个类内初始值(in-class initializer)。
编写自己的头文件
- 头文件通常包含哪些只能被定义一次的实体:类、
const
和constexpr
变量。
预处理器概述:
预处理器(preprocessor):确保头文件多次包含仍能安全工作。
当预处理器看到
#include
标记时,会用指定的头文件内容代替#include
头文件保护符
(header guard):头文件保护符依赖于预处理变量的状态:已定义和未定义。
#ifdef
已定义时为真#ifndef
未定义时为真- 头文件保护符的名称需要唯一,且保持全部大写。养成良好习惯,不论是否该头文件被包含,要加保护符。
1 |
|