第1章 开始

本章以一个实际问题,书店问题,来简单的介绍C的基本特性。这个问题的代码将贯彻整本书,后面的章节会逐一讲解代码中涉及到的 C 语言特性。

这个问题具体,就是要保存书店的所有销售记录的档案,每条记录保存了某本书的一次销售信息,包含三个数据:

0-201-70353-X  4   24.99

它们分别是书的 ISBN,售出的册数,书的单价。有时,书店老板需要查询此档案,计算每本书的销售量、销售额及平均售价。

这个问题会涉及到的有:

  • 定义变量
  • 进行输入和输出
  • 使用数据结构保存数据
  • 检测两条数据是否有相同的 ISBN
  • 包含一个循环来处理销售档案中的每条记录

我们首先介绍如何使用 C++ 来解决这些子问题,然后编写书店程序。

1.1 一个简单的C++程序

int main()
{return 0;
}

由这个程序简单的介绍函数的定义。

函数定义包含四部分:

  1. 返回类型
  2. 函数名
  3. 一个括号包围的形参列表 (允许为空)
  4. 函数体

main 是一个比较特殊的函数,但其定义与其他函数是一样的。

1.1.1 编译、运行程序

上图是一个 C 程序(C++ 程序类似)的编译过程,可供参考。

1.2 初识输入和输出

在C++语言中并未定义任何输入输出(IO)语句,但提供了全面的标准库来提供 IO 机制,即iostream库。

iostream 库包含两个基础类型 istream 和 ostream ,分别表示输入流和输出流

(当然,我们也可可以使用 C 语言中的 printf ,C ++是兼容 C 的,但是并不建议,所以本书中的示例都是使用标准库中提供的 iostream 库。)

标准输入输出对象

标准库定义了4个 IO 对象:

  • 输入:istream  类型的对象中的 cin
  • 输出:ostream 类型的对象中的  cout、cerr 、clog 。

几个符号介绍:

  • <<         输出运算符
  • >>         输入运算符
  • endl        为操纵符,效果是结束当前行,并将与设备关联的缓冲区中的内容刷到设备中。
  • ::        作用域运算符

示例:

#include <iostream>int main(){std::cout << "Enter two numbers:" << std::endl;int v1 = 0, v2 = 0;std::cin >> v1 >> v2;std::cout << "The sum of " << v1 << " and " << v2<< " is " << v1 + v2 << std::endl;return 0;}

1.3 注释

c++的两种注释:

//  单行注释1
std::cout << "hello" << std::endl;/*
* 多行注释2
*/
std::cin >> val;

1.4. 控制流

循环和判断语句

4.1 while

示例: 求 1 到 10 的和

#include <iostream>
int main()
{int sum = 0, val = 0;while (val <= 10){sum += val;++val;}std::cout << "Sum of 1 to 10 inclusive is "<< sum << std::endl;return 0;
}

4.2  for

示例:for 循环, 将上面 while 循环改成  for

#include <iostream>
int main()
{int sum = 0; for (int val = 1; val <= 10; ++val)sum += val;std::cout << "Sum of 1 to 10 inclusive is "<< sum << std::endl;return 0;}

4.3 读取数量不定的输入数据

#include <iostream>
int main()
{int sum = 0, val = 0;while (std::cin >> val)sum += val;std::cout << "Sum  is " << sum << std::endl;return 0;
}
上面程序在输入结束后除了按回车键,还需要按结束符

结束符:

  • 在Windows 系统中输入结束符是按 Ctrl + Z
  • 在 UNIX系统中,包括 Mac OS X 系统中,文件结束符输入用 Ctrl + D

4.3 if

示例:读取一串数据并统计每个数字出现的次数 ,windows下的终止键 ctrl + z

#include <iostream>
int main()
{// currVal是我们正在统计的数,我们将读入的新值存入valint currVal = 0, val = 0;// 读取第一个数,并确保确实有数据可以处理if(std::cin >> currVal) {int cnt = 1;while(std::cin >> val) {if(val == currVal) {++cnt;}else {std::cout << currVal << " occurs " << cnt << " times " << std::endl;currVal = val;cnt = 1;}}std::cout << currVal << " occurs " << cnt << " times " << std::endl;}return 0;
}

1.5 类的简介

在C++中,我们通过定义类 来定义自己的数据结构。

一个类定义了一个类型(类类型),以及与其关联的一组操作。

本节中我们使用一个类,需要了解三件事:

●类名是什么?
●它是在哪里定义的?
●它支持什么操作?

对于书店程序来说,我们假定类名为 Sales_item,头文件 Sales_item.h 中已经定义了这个类。

1.5.1 Sales_item 类

Sales_item 类的作用是表示一本书的销售总额、售出册数和评价售价。我们现在不需要关心这些

数据是如何存储、如何计算,我们只需要直了解怎么使用它。

我们可以把一个类当作是一种类型(类类型),就和C++语言内置的类型( int float 等)一样,其

用法也是和内置类型一致的。

定义类类型的变量

        Sales_item item;

上面语句表达的是定义了一个变量 (对象)item,它的类型是 Sales_item 类型。

除了可以定义 Sales_item 类型变量之外,我们还可以:

  • 调用一个名为 isbn 的函数从一个 Sales_item 对象中提取 ISBN 书号
  • 用输入运算符(>>)和输出运算符 (<<) 读、写 Sales_item 类型的对象
  • 用赋值运算符 (=)将一个 Sales_item 对象的值赋予另一个 Sales_item 对象
  • 用加法运算符 (+) 将两个 Sales_item 对象相加。两个对象必须表示同一本书(相同的 ISBN)。加法的结果是一个新的 Sales_item 对象,其 ISBN 与两个运算符对象相同,而其总销售额和售出册数是两个运算对象的对应值之和
  • 使用复合运算符 (+=) 将一个 Sales_item 对象加到另一个对象上。

读写 Sales_item

把 Sales_item 当做是一个普通的变量(对象),它的操作和内置类型是一样的

如果输入:

0-201-70353-X 4 24.99

则输出为:

0-201-70353-X 4 99.96 24.99

99.96 表示的是以每本22.49的价格售出 4 本的总销售额。

Sales_item 对象的加法

像操作两个int 变量一样,将两个 Sales_item 对象相加

输入:


#include <iostream>
#include "../Sales_item.h"
int main()
{Sales_item item1, item2;std::cin  >> item1 >> item2;   // 读取一对交易记录std::cout << item1 + item2 << std::endl;    // 打印它们的和return 0;
}

输出:
0-201-78345-X 3 20.00
0-201-78345-X 2 25.00

1.5.2 初识成员函数

将两个 Sales_item 对象相加的程序,首先应该检查两个对象是否具有相同的 ISBN,Sales_item 提供了获取 ISBN 的方法(成员函数)


#include <iostream>
#include "../Sales_item.h"
int main()
{Sales_item item1, item2;std::cin >> item1 >> item2;if(item1.isbn() == item2.isbn()) {std::cout << item1 + item2 << std::endl;return 0;} else {std::cerr << "Data must refer to same ISBN" << std::endl;return -1;}
}

什么是成员函数?

item1.isbn() == item2.isbn()

成员函数是定义为类的一部分的函数,有时也称为方法。

我们通常是使用一个类对象来调用成员函数,如下代码,使用点运算符  .  来调用对应的成员

iterm1.isbn();

1.6 书店程序

#include <iostream>
#include "../Sales_item.h"
int main()
{Sales_item total;       // 保存下一条交易记录的变量// 读入第一条交易记录,并确保有数据可以处理if (std::cin >> total) {Sales_item trans;       // 保存和变量// 读入并处理剩余交易记录while (std::cin >> trans){// 如果我们仍在处理相同的书if (total.isbn() == trans.isbn())total += trans;     // 更新总销售额else {// 打印前一本的结果std::cout << total << std::endl;total = trans;      // total 现在表示下一步书的销售额}}std::cout << total << std::endl;    // 打印最后一本书的结果}else{// 没有输入!警告读者std::cerr << "No data?!" << std::endl;return -1;  // 表示失败}return 0;
}

第1部分 c++基础

第2章

2.1 基本内置类型

c++基本数据类型包含两类:

  • 算术类型(arithmetic type)——字符、整型、布尔型、浮点型
  • 空类型(void)——不对应具体的值,仅用于一些特殊场合。例如函数不返回值。

2.1.1 算术类型

算术类型包含两类:

  • 整型——包括字符、布尔型在内
  • 浮点型

算术类型的尺寸:该类型数据所占的比特数。

尺寸在不同机器上是不同的。

某一类型所占的尺寸不同,所能表示的数据范围也不一样。

下图列出了c++标准规定的尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。

 带符号类型:

可以表示正数、负数、0

无符号类型:

仅能表示正数、0

特别注意:

类型char和类型signed char并不一样。尽管字符型有char 、signed char、unsigned char,但是字符的表现形式却只有两种:带符号的和无符号的。char类型实际上会表现为上述两种形式中的一种,具体是哪种由编译器来决定。因此在使用char时要尤其注意。

建议:如何选择类型

宗旨:做出限定从而简化选择过程。

  • 当明确数值不可能为负时,选择无符号类型;
  • 使用int执行整数运算;-----在实际应用中,short常常显得太小,而long一般和int有一样的尺寸。如果你的数值超过了int范围,就选用long long
  • 在算术表达式中不要使用char或bool,只有在存放字符或布尔值时才使用它们。因为类型char的尺寸与机器相关,在一些机器上是有符号的,而在另一台即使上有是无符号的,使用char进行运算特别容易出问题。如果需要使用一个不大的整数,那么明确指定它的类型是signed char或unsigned char。
  • 执行浮点数运算选用double。因为float通常精度不够,而且双精度浮点数和单精度浮点数的计算代价相差无几。事实上,对于某些机器来说,双精度运算甚至比单精度还快。long double 提供的精度在一般情况下是没有必要的,况且它带来的运算消耗也不容忽视。

2.1.2 类型转换

定义:

将对象从一种给定的类型转换为另一种相关类型。

当在程序中使用一种类型而其实对象应该取另一种类型时,程序会自动进行类型转换。

bool b = 42;        // b 为真
int i = b;         // i 的值为3
i = 3.14;          // i 的值为3
double pi = i;     // pi 的值为 3.0
unsigned char c = -1;  // 假设 char 占8bit,c 的值为255? 书上是这个结果,但我运行打印没有值
signed char c2 = 256;  // 假设 char 占8bit, c2 的值是为定义的 我运行打印没有值

含有无符号类型的表达式

  • 切勿混用有符号和无符号类型。会造成错误,有符号类型数会转换成无符号数。示例1
  • 无符号数中减去一个数时,不管这个数是不是无符号数,都必须保证结果不是负值。示例2
  • 注意无符号数用在for循环,可能造成死循环。因为无符号数不会小于0。可以用while来替代for。示例3

示例1:

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl;   // 输出 -84
std::cout << u + i << std::endl;   // 如果 int 占 32bit,输出 4294967264

示例2:

 unsigned u1 = 42, u2 = 10;cout << u1 - u2 << endl;// 正确,输出32cout << u2 - u1 << endl;// 正确,但输出 4294967264 是取模后的值

示例3:

    // 错误 变量u永远也不会小于0,循环条件一直成立/*for (unsigned u = 10; u >= 0; u--) {cout << u << endl;}*/unsigned u = 11;while (u > 0) {--u;// 先减1,这样最后一次迭代就会输出0,也不会死循环,不会有期望u小于0的情况cout << u << endl;}

2.1.3 字面常量

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

整型字面值:

  • 八进制:0开头
  • 十六进制:0x或0X开头
  • 十进制

20        // 十进制
        024        // 八进制
        0x14    //     十六进制

整型字面值具体的数据类型由它的值和符号决定。默认情况下十进制字面值是带符号数,八进制和十六进制字面值可能是带符号或无符号的。字面值的类型是其对应类型中尺寸最小的那个。

浮点型字面值:

  • 默认字面值是一个double。
  • 可以用后缀来表示其他浮点型。

字符字面值:

  • 单引号括起来构成

'a'        // 字符字面值

字符串字面值:

  • 双引号括起来构成
  • 实际上由常量字符构成的数组
  • 编译器在每个字符串结尾处添加一个空字符('\0')

"Hello World!"  // 字符串字面值

转义序列:

需要用到转义序列的有两类场景:

  • 不可打印的字符。退格、其他控制字符等
  • c++语言中特殊含义的字符。单引号、双引号、问号、反斜线。

指定字面值的类型

我们可以通过下表的符号来指定字面值的类型

示例:

L'a'       // 宽字符型字面值,类型是 wchar_t
u8"hi!"       // utf-8 字符串字面值
42ULL       // 无符号整型字面值,类型是 unsigned long long
1E-3F       // 单精度浮点型字面值,类型是 float
3.14159L    // 扩展精度浮点类型字面值,类型是 long double

布尔字面值:

true 和 false

指针字面值:

nullptr

2.2 变量

变量提供一个具名的、可供程序操作的存储空间。

2.2.1 变量定义

变量定义语法:

类型说明符  变量名1 = 初值, 变量名2,  ……;

初始化含义:

当对象在创建时获得了一个特定的值,就是对象被初始化了。

赋值的含义:

把对象的当前值擦除,而以一个新值来替代。

注意:

初始化和赋值是两个完全不同的操作。不可混淆。原因看两者的定义可知晓。

示例1:变量定义

int sum =0, value,    // sum、value、units_sold 都是intunits_sold = 0;   // sum 、 units_sold 初值都是0
Sales_item item;      // item 的类型是 Sales_item
string book("0-201-78345-X");    // book通过string字面值初始化

示例2:初始化和赋值是两个完全不同的操作

 // 定义及初始化// 正确 a先被定义并被赋初值,然后a被用于初始化bint a = 2, b = a * 3;// 赋值,覆盖原来的旧值,用新值来替代旧值b = 20;

列表初始化

c++语言定义了初始化的的好几种形式:

int units_sold n = 0;
int units_sold n = {0};  // 列表初始化
int units_sold n{0};   // 列表初始化
int units_sold n(0); 

用花括号来初始化,是c++11标准的一部分,称为 列表初始化

列表初始化的重要特点:

        若初始值存在丢失信息的风险时,编译器会报错。

 long double ld = 3.1415926;int a{ ld }, b = { ld };// 编译报错,转换未执行,因为存在丢失信息的风险int c(ld), d = ld;// 正确,转换执行,且会丢失部分值。

默认初始化

定义变量时没有初始化变量的值,则变量会被默认初始化。默认初始化的值取决于变量定义的类型及定义变量的位置。

注意:

定义在函数体内的局部变量和类中的成员属性是不会被初始化的 (不同编译器的实现可能会不同), 所以不用试图使用任何方式去访问这些变量。

建议:

初始化每一个内置类型的变量,防止出错。

示例:

#include <iostream>
using namespace std;int init;
string str;struct A {int m;string m_str;void print(){// 编译通过,但 打印的值很奇怪,不建议未初始化直接使用cout<<"m: " << m << endl;// 正确,打印空串cout <<"m_str:"<< m_str << endl;}
};void test()
{int un_init;// 编译报错//cout<<"un_init:" << un_init << endl;
}void test01()
{A a;a.print();
}int main()
{test();test01();// 正确,初始化为0cout << init << endl;// 正确,初始化为空串cout << str << endl;system("pause");return 0;
}

2.2.2 变量声明和定义的关系

区分声明和定义的原因:

        为了支持分离式编译。允许程序分割为若干个文件,每个文件可被独立编译。

声明:

        使程序知道名字。

定义:

        创建与名字关联的实体。

声明与定义的相同点:

规定了变量的类型和名字。

声明与定义的区别:

  • 定义申请了存储空间,也可能会赋初始化值。
  • 变量能且只能被定义一次,但是可以被声明多次。

示例:

#include <iostream>
#include <string>using namespace std;extern int i; // 声明
extern int i; // 声明可多次
extern int j = 1; // 声明并定义
int x; // 声明并定义
//int x; // 报错,只能定义一次void test() {extern int c;// 声明extern int c;// 声明多次// 直接报错 不允许对外部变量的局部申明//extern int a = 1;
}int main() {// 编译报错 无法解析的外部命令// 只声明未定义//cout << i << endl;//编译报错  在函数内部,不能初始化一个由extern声明的变量,//i = 0;// 打印1cout << j << endl;// 打印 0cout << x << endl;test();system("pause");return 0;
}

2.2.3 标识符

标识符:通俗来讲,就是变量名。

书写标准:

  • 由字母、数字和下划线组成,且必须以字母或下划线开头。
  • 不能使用c++保留关键字
  • 不能连续出现两个下划线
  • 不能以下划线紧连大写字母开头
  • 定义在函数体外的标识符不能以下划线开头。

建议命名规范:

  • 普通的局部变量和函数参数名使用小驼峰(第一个单词首字母小写,其他单词首字母大写), 例: userName
  • 全局变量前加 g_, 后面的按小驼峰规则 , g_userName
  • 静态变量前加 s_ , 后面按小驼峰规则, s_userName
  • 类名使用大驼峰,所有单词的首字母大写 ,  UserManage
  • 类属性(成员变量)前面加 m_ ,后面按小驼峰规则  , m_userName
  • 常量全部使用大写,多个单词用_ 分割, MAX_NUMBER

示例 标识符书写:

#include <iostream>
#include <string>using namespace std;int _name1=1;int main() {// 符合规范的标识符int name2_ = 2;int _name3 = 3;int __name4 = 4;int _Name5 = 5;cout << "_name1 = " << _name1 << endl;cout << "name2_ = " << name2_ << endl;cout << "_name3 = " << _name3 << endl;cout << "__name4 = " << __name4 << endl;cout << "_Name5 = " << _Name5 << endl;system("pause");return 0;
}

2.2.4 名字的作用域

作用域:

c++中多数作用域都以花括号分隔。

注意:

  • 函数内部可以重新定义全局变量,但实际使用时,局部变量不宜与全局的变量重名
  • 嵌套的块,允许在内层作用域中重新定义外层作用域已有的名字。但实际使用时,内部的最好不要和外部的重名。
  • 万一变量重名,变量取值会采取就近原则。

2.3 复合类型

复合类型:基于其他类型定义的类型。

2.3.1 引用

引用:就是为变量起一个别名。

语法:

数据类型 a = 初始值;

数据类型 &b = a;

特点:

  • 对引用进行的所有操作,都是在与之绑定的对象上进行的。
  • 为引用赋值,实际上是把赋值给了与引用绑定的对象。
  • 可以在一条语句中定义多个引用。必须以&开头。

注意:

  • 引用必须初始化
  • 引用类型的初始值必须是一个对象,不能是常量。
  • 无法令引用重新绑定到另外一个对象。
  • 引用的类型必须和绑定对象是一致的。
  • 不能定义引用的引用

示例:

    int ival = 1024;int ival2 = 1000;int& refval = ival;// 正确 refval引用并没有改变,只是改变了指向的变量ival的值,使得ival = ival2了。refval = ival2; //int& refval2; //错误 引用必须被初始化 cout << "ival = " << ival << endl; // 1000cout << "ival2 = " << ival2 << endl; // 1000cout << "refval = " << refval << endl; // 1000
    int i = 1, i2 = 2;// i 和 i2都是int变量int& r1 = i, r2 = i2;// r1是i的引用,r2是int变量且值与i2相同int i3 = 1, & ri = i3;// i3是int变量,ri是i3的引用int& r3 = i3, & r4 = i2;//r3是i3的引用,r4是i2的引用
 //int& refval4 = 10; // 错误,引用类型的初始值必须是一个对象,不能是常量double d = 3.14; //int& refval5 = d; // 错误,引用的数据类型必须与所绑定对象的类型一致

2.3.2 指针

(1) 指针概念

指针是“指向(point to)”另外一种类型的复合类型。

指针与引用相同点:

指针也实现了对其他对象的间接访问。

指针与引用不同点:

指针本身就是一个对象,允许对指针赋值和拷贝,且可以修改指向的对象。而引用一定定义,就无法再绑定到另外的对象。

指针无须在定义时赋初值。而引用必须在定义时赋初值。

定义语法:

数据类型 *变量名;

示例:

int *p1, *p2; // p1 和 p2 都是指向int类型对象的指针
double d1, *d2; // d2 是指向 double 类型对象的指针; d1 是double型对象

(2)获取对象的地址

指针存放的是:某个对象的地址。要想获取该地址,需要使用取地址符(&)。

示例:

#include <iostream>
#include <string>using namespace std;void test() {int ival = 42;int* p = &ival;// p存放变量ival的地址,或者说p是指向变量ival的指针double dval;double* pd = &dval;// 正确 初始值是double型对象的地址double* pd2 = pd;// 正确 初始值是指向double对象的指针//int* pi = pd; // 错误 指针pi的类型与pd的类型不匹配// 指针存放的是对象的地址// pd和pd2都是指向的是dval的地址dval = 20;// 打印值cout << "dval = " << dval << endl;// 打印dval值cout << "*pd  = " << *pd << endl;// 解引用打印pd指向对象的值cout << "*pd2  = " << *pd2 << endl;// 解引用打印pd2指向对象的值cout << "&dval = " << &dval << endl;// 打印dval地址cout << "pd  = " << pd << endl;// 打印pd指向地址cout << "pd2 = " << pd2 << endl;// 打印pd指向地址// 修改pd指针指向对象的值*pd = 30;cout << "dval = " << dval << endl;cout << "*pd  = " << *pd << endl;cout << "*pd2  = " << *pd2 << endl;cout << "&dval = " << &dval << endl;cout << "pd  = " << pd << endl;cout << "pd2 = " << pd2 << endl;
}int main() {test();system("pause");return 0;
}

结果:

dval = 20
*pd  = 20
*pd2  = 20
&dval = 003AFDD8
pd  = 003AFDD8
pd2 = 003AFDD8
dval = 30
*pd  = 30
*pd2  = 30
&dval = 003AFDD8
pd  = 003AFDD8
pd2 = 003AFDD8

(3)指针值

指针的值(即地址)应属下列4种状态之一:

  1. 指向一个对象
  2. 指向紧邻对象所占空间的下一个位置
  3. 空指针,意味着指针没有指向任何对象
  4. 无效指针也就是上述情况之前的其他值。

注意:不能试图访问2、3、4类指针。如果访问了,后果无法预计。

(4)利用指针访问对象

*:解引用符

如果一个指针指向了一个对象,可以用*来解引用访问。

注意:解引用操作仅适用于确实指向了某对象的有效指针。

    int ival = 42;int* p = &ival;cout << ival << endl; // 42  p存放着变量ival的地址,或者说p是指向ival的指针cout << *p << endl; // 42 由符号*得到指针p所指的对象*p = 0;cout << ival << endl; // 0 *p可以得到所指的对象,所以可以通过*p可以给对象ival赋值cout << *p << endl; // 0 

(5)空指针

空指针不指向任何对象。在操作指针之前必须确认为非空指针。

生成非空指针的方法有:

 // 三种生产空指针的方法:// 建议用第一种int* p = nullptr; // 等价于 int *p1 = 0; 是c++11新标准,利用字面值nullptr来初始化指针。int* p2 = 0; // 直接将p2初始化为直面值常量0//需要首先#include cstdlibint* p3 = NULL; // 等价于 int *p3 = 0;  NULL是预处理变量,值也是0。//int zero = 0;//int* p4 = zero;// 错误 不能把int变量直接赋给指针

使用指针建议:

初始化所有指针,并且尽量等定义了对象之后再定义指向它的指针。

如果实在不清楚指针应该指向何处,就把它初始化为nullptr或者0,这样程序就能检测并知道它没有指向任何具体的对象了。(当一个指针初始化为nullptr或者0后,若在没有指向某个对象情况下使用了,编译器会明确报错)

(6)赋值和指针

给指针赋值就是令它存放一个新的地址,从而指向一个新的对象。

 int i = 42;int* pi = 0; // pi被初始化,但并没有指向任何对象int* pi2 = &i; // pi2被初始化,并赋值为i的地址。或者说指针pi2指向变量i.int* pi3; // 定义指针pi3,但未被初始化。如果在块内,pi3的值是无法确定的pi3 = pi2; // pi3和pi2指向同一个对象pi2 = 0; // pi2改为不指向任何对象cout << *pi3 << endl; // 42//cout << *pi << endl; // 编译报错//cout << *pi2 << endl; // 编译报错

有时候很难搞清楚一条赋值语句,是改变了指针的值,还是改变了指针所指对象的值。

最好的办法是:记住赋值永远改变的是等号左侧的对象。

    int ival = 42;int* pi = 0;// pi的值被改变,pi现在保存的是ival变量的地址。pi指针指向的是ival变量pi = &ival; // *pi解引用,代表的是ival这个变量。ival的值被改变,指针pi并没有变,还是存的ival的地址。*pi = 0; 

(7)其他指针

只要指针拥有一个合法值,就能将它用在条件表达式中。如果指针为0,条件取为false,任何非0的指针,条件都取true。

int ival = 1024;
int *pi = 0; // pi 合法,是一个空指针
int *pi2 = &ival; // pi2 合法,存放着ival的地址if(pi)    // pi的值为0,因此条件为false// ……
if(pi2)    // pi2的值为ival的地址,不是0,因此条件为true// ……

对于两个类型相同的合法指针,可以用 == 或 != 来比较,其结果就是bool型。

(8)void* 指针

void*是一种特殊的指针类型,可以用于存放任意对象的地址。

void*能做的事情比较有限,只能用于:

  1. 拿它和别的指针比较
  2. 作为函数的输入和输出
  3. 赋给另外一个void*指针

注意:不能直接操作void*指针所指对象。

 double obj = 3.14, * pd = &obj;int i = 42;void* pv = &obj;pv = pd;pv = &i;// cout << *pv << endl; // 报错

2.3.3 理解复合类型的声明

变量的定义包括一个基本数据类型和一组声明符。

int i = 1, *p = &i, &r =i;

int 是数据类型,*  和 第二个& 是声明符,第一个&是取址符。

(1)定义多个变量

int* p; // 合法,但是容易产生误导。基本数据类型是int而非*int。* 仅仅是修饰了p而已。
int* p1,p2; // p1是指向int的指针,p2是int类型变量。// 最好写成
// int *p1,p2; 但是有的编辑器会自动修改了格式。

(2)指向指针的指针

 int ival = 1;int* pi = &ival; //  pi指向一个int型变量int** ppi = &pi; //  ppi指向一个int型指针。指向指针的指针// 使用三种方式输出ival值cout << ival << endl; // 1cout << *pi << endl; // 1cout << **ppi << endl; // 1

(3)指向指针的引用

    int i = 42;int* p; // p是一个int型指针int *&r = p; // r是一个对指针p的引用 。r = &i; // r是指针p的引用,所以r保存的是一个地址,这里给r赋值&i,就是令指针p指向i*r = 100; // r是指针p的引用,所以解引用r与解引用p意义一样,会得到i,这里就是将i的值改为100cout << i << endl; // 100 cout << *p << endl; // 100 cout << *r << endl; // 100 cout << &i << endl; //  打印i的地址 cout << p << endl; // 打印i的地址 cout << r << endl; // 打印i的地址 

如何理解:int *&r

要理解r的类型到底是什么,可以从右往左阅读 r 的定义,离变量名最近的符号对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用于确定r引用的类型是什么,这里*说明r引用是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int指针。

2.4 const限定符

const对象一旦创建后其值就不能改变,所以const对象必须初始化。

const int buffsize = 512;
const int i = get_size(); // 正确 用函数返回值初始化,运行时初始化
const int j = 42; // 正确 编译时初始化
const int k; // 错误,未初始化

const对象仅在文件内有效,如果其他文件要使用,需在变量的定义之前加extern关键字。

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

2.4.1 const的引用

把引用绑定到const对象上,称为对常量的引用。

与普通引用的区别:不能被用作修改它所绑定的对象。

const int ci = 1024;
const int &r1 = ci;     // 正确 引用及其对应的对象都是常量
r1 = 42;                // 错误 r1是对常量的引用,不能修改值
int &r2 = ci;          // 错误 试图让一个非常量引用绑定一个常量对象// 假设r2合法,则可以通过r2来改变它引用的对象ci,这显然是错误的。

初始化常量引用,允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。也允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。

int i =42;
const int &r1 = i;          // 正确 运行将常量引用绑定到普通int对象上
const int &r2 = 42;         // 正确 r2是一个常量引用,可以初始化为字面值
const int &r3 = r1 * 2;     // 正确 r3是一个常量引用,可以初始化为一般表达式
int &r4 = r1 * 2;           // 错误 r4是一个普通引用
int &r5 = r1;               // 错误 r5是一个普通引用,不能指向常量对象
r1 = 10;                    // 错误 不能修改常量引用

常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不做限定的。

int i = 42;
int &r1 = i;
const int &r2 = i;     // r2也绑定对象i,但是不运行通过r2修改i的值
r1 = 0;                // 正确 r1并非常量,i的值可以修改为0
r2 = 0;                // 错误 r2是一个常量引用,不能修改值

2.4.2 指针和const

2.4.3 顶层const

2.4.4 constexpr和常量表达式

2.5 处理类型

2.5.1 类型别名

2.5.2 auto类型说明符

2.5.3 decltype类型指示符

2.6 自定义数据结构

2.6.1 定义Sales_data类型

2.6.2 使用Sales_data类

2.6.3 编写自己的头文件

第3章
第4章
第5章
第6章
第7章

第二部分 c++标准库
第8章
第9章
第10章
第11章
第12章

第三部分 类设计者的工具
第13章
第14章
第15章
第16章

第四部分 高级主题
第17章
第18章
第19章

c++ primer 5th 笔记相关推荐

  1. c++ primer 5th 笔记:第二章

    第二章:变量和基本类型 笔记: 1. c++语言规定一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少和一个long一样大.其中,数据类型long l ...

  2. C++ Primer 5th笔记(chap 18 大型程序工具) 重载与命名空间

    1. using 声明或 using 指示能将某些函数添加到候选函数集 2. 对于接受类类型实参的函数来说, 其名字查找将在实参类所属的命名空间中进行.在这些命名空间中所有与被调用函数同名的函数都将被 ...

  3. C++ Primer 5th笔记(chap 18 大型程序工具)使用命名空间成员

    1. 3种方法 using 声明 命名空间的别名( namespace alias) using 指示( using directive) 1.1 命名空间的别名 namespace cplusplu ...

  4. C++ Primer 5th笔记(chap 18 大型程序工具)构造函数与虚继承

    1. 继承体系中的每个类都可能在某个时刻成为" 最低层的派生类". 只要我们能创建虚基类的派生类对象, 该派生类的构造函数就必须初始化它的虚基类. Bear::Bear (std: ...

  5. C++ Primer 5th笔记(chap 18 大型程序工具)多重继承下的类作用域

    1. 派生类的作用域嵌套在直接基类和间接基类的作用域中. 查找过程沿着继承体系自底向上进行, 直到找到所需的名字.派生类的名字将隐藏基类的同名成员. 在多重继承的情况下, 相同的查找过程在所有直接基类 ...

  6. C++ Primer 5th笔记(chap 18 大型程序工具)虚继承

    1. 问题 派生类可以多次继承同一个类. 派生类可以通过它的两个直接基类分别继承同一个间接基类, 也可以直接继承某个基类, 然后通过另一个基类再一次间接继承该类.如果某个类在派生过程中出现了多次, 则 ...

  7. C++ Primer 5th笔记(chap 18 大型程序工具)类型转换与多个基类

    1. 在只有一个基类的情况下, 派生类的指针或引用能自动转换成一个可访问基类的指针或引用. 我们可以令某个可访问基类的指针或引用直接指向一个派生类对象. eg. 一个ZooAnimal. Bear 或 ...

  8. C++ Primer 5th笔记(chap 18 大型程序工具) 多重继承之构造函数、析构函数

    1. 继承的构造函数与多重继承 如果从多个基类中继承了相同的构造函数(即形参列表完全相同),则程序将产生错误 struct Basel {Basel () = default;Basel (const ...

  9. C++ Primer 5th笔记(chap 18 大型程序工具) 多重继承与虚继承

    1. 多重继承 (multiple inheritance) 一个类从多个直接基类派生 eg. class Bear : public ZooAnimal {class Panda : public ...

  10. C++ Primer 5th笔记(chap 17 标准库特殊设施)流随机访问

    1. 流类型通常都支持对流中数据的随机访问. 可以重定位流, 使之跳过一些数据, 首先读取最后一行, 然后读取第一行, 依此类推. 1.1 标准厍提供了一对函数, 来定位( seek )到流中给定的位 ...

最新文章

  1. python3 base64 加解密
  2. 一文读懂 Spring的前世今生
  3. tfw文件如何导入cad_如何将CAD的线稿导入PS并和底色分离
  4. 夜读丨72名研究生被清退:孩子,你前半生偷的懒,后半生得拼命还
  5. js 判断一个字符在字符串中出现的次数 - 代码篇
  6. web.xml mysql_JSP登录验证脚本失败(mysql后端)web.xml servlet映射?
  7. 买得起修不起?华为Mate X 5G维修价格公布:被吓到了
  8. window.location.href重定向 不会触发webview
  9. 售票系统的组件图和部署图_实物图+电气图讲解:教你学会看配电系统图,值得收藏!...
  10. 电子计算机属于哪个税目,高拍仪,摄像头等 税收分类编码是什么?具体选择哪一个计算机外部设备?...
  11. 抓包工具神器,fiddler全解
  12. 2022-2027年中国DTP药房行业市场全景评估及发展战略规划报告
  13. sunshine in the rain
  14. Queuing HDU2604
  15. icloud android,如何将照片从 iCloud 转移到 Android
  16. 基于自定义注解校验入参Model中的必传字段
  17. 【机器视觉】OpenCV-Python 图像的噪声处理
  18. 爱情指数测试脸型软件,性格解析测试 从6类脸型中透视出你的性格及爱情
  19. POJ2720_Last Digits_欧拉降幂公式打表
  20. JavaScript原型链(重要)

热门文章

  1. 海格里斯HEGERLS库架合一|仓储托盘四向穿梭车在冷库冷链行业中的应用
  2. 程序员:面试时,我特么都不敢说我是写公众号的
  3. Edge 联动 VsCode,这功能爱了
  4. 计算机创建网络连接,小编教你电脑怎么创建宽带连接_电脑创建宽带连接的方法...
  5. rk3568 适配摄像头 (双摄)
  6. 把客户当成自己的朋友 (销售中的心理学)
  7. Java进阶之光!mysql解压安装教程windows
  8. Ingress详细介绍
  9. 在RISC-V星光板上创建Debian系统镜像
  10. HTML - 浅谈DIV盒子居中