精读《 C++ Primer》笔记(一):1-5章 输入输出,指针,头文件,string,vector

来源:互联网 发布:sketch 激活码 mac 编辑:程序博客网 时间:2024/05/21 06:37

前言

之前,已经将菜鸟教程中的C++课程和慕课网中的C++课程学习完毕。

但是,对于C++与其他已学语言之间的不同之处,还需要深入体会。

于是开始精读《C++ Primer 中文版 第5版》,将需要注意的与其他语言不同之处,记录于此。(根据书中的结构来记录)

注意:此笔记不能作为学习C++语言的教程。请阅读之前写过的C++学习笔记后,或是有了C++基础后,再来阅读此系列文章。

1.2 初识输入输出

C++语言并未定义任何输入输出(IO)语句(比如C语言中的printf(),scanf();Java语言中的System.out.print(),Syetem.in.read();Python语言中的print(),input()),取而代之,包含了一个全面的标准库来提供IO机制。

iostream库包含两个基础类型istream和ostream,分别表示输入流和输出流。一个流就是一个字符序列,是从IO设备读出或写入IO设备的。术语“流”想要表达的是,随着时间的推移,字符是顺序生成或消耗的

标准输入输出对象

为什么叫做标准输入输出对象?

是因为输入输出对象是标准库中定义的。(每个语言都有它自己的标准库,当然用户也可以自己写自己的输入输出方法,此时就不叫做标准输入输出)

标准库定义了4个IO对象。为了处理输入,我们使用一个名为cin的istream类型的对象。这个对象也被称为标准输入

对于输出,我们使用一个名为cout的ostream类型的对象。这个对象也被成为标准输出

标准库还定义了其他两个ostream对象,名为cerr和clog。我们通常用cerr来输出警告和错误信息,因此它也被称为标准错误。而clog用来输出程序运行时的一般性消息

一个使用IO库的程序

提示用户输入两个数,然后输出它们的和:

#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;}

输出运算符(<<)

下面这条语句中的表达式使用的输出运算符(<<)在标准输出上打印消息:

std::cout<<"Enter two numbers:"<<std::endl;

<<运算符接受两个运算对象:左侧的运算对象必须是一个ostream对象右侧的运算对象是要打印的值

此运算符将给定的值写到给定的ostream对象中。输出运算符的计算结果就是其左侧运算对象。

操纵符(endl)

例:

    std::cout<<std::endl;

这个运算符打印endl,这是一个被称为操纵符的特殊值

写入endl的效果是结束当前行(即换行),并将与设备关联的缓冲区中的内容刷到设备中。

缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流

作用域运算符( : : )

标准库定义的所有名字都在命名空间std中

当使用标准库中的一个名字时(如果没有使用using namespace std,导入该命名空间的话),必须显式说明我们想使用来自命名空间std中的名字

例如,需要写std::cout,通过使用作用域运算符(: :)来指出我们想使用定义在命名空间std中的名字cout

输入运算符(>>)

>>运算符接受两个运算对象:它接受一个istream对象作为其左侧运算对象接受一个对象作为其右侧运算对象

它从给定的istream读入数据,并存入给定对象中

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

例,当我们预先不知道要对多少个数字求和,这就需要不断读取数据直至没有新的输入为止:

#include<iostream>int main(){    int sum=0,value=0;    while(std::cin>>value)        sum+=value;    std::cout<<"Sum is:"<<sum<<std::endl;    return 0;}

此时,如果我们输入

3 4 5 6

则程序会输出

Sum is:18

当我们使用一个istream对象作为条件时,其效果是检测流的状态。如果流是有效的,即流未遇到错误,那么检测成功

当流遇到文件结束符,或遇到一个无效输入时(例如读入的值不是一个整数),那么istream对象的状态会变为无效。处于无效状态的istream对象会使条件变为假。

从键盘输入文件结束符

对于如何指出文件结束,不同操作系统有不同的约定。

在Windows系统中,输入文件结束符的方法是敲Ctrl+Z,然后按住Enter或Return键。

在Unix系统中,包括Mac OS X系统中,文件结束符输入是用Ctrl+D。

2.2 变量

初始值

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

2.3 复合类型

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

2.3.1 引用

引用为对象起了另外一个名字(即对引用做的任何操作,都是对那个引用对象做出的操作),引用类型引用另外一种类型。

通过将声明符写成&d的形势来定义引用类型,其中d是声明的变量名:

int ival=12;int &refval=ival;   //refval指向ival(是ival的另一个名字)

引用必须被初始化(即int &refval2;是不对的)

一旦初始化完成,引用将和它的初始值对象一直绑定在一起(即无法将令引用重新绑定到另外一个对象)。

2.3.2 指针

指针是“指向”另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。

然而,指针与引用相比又有很多不同点。

  1. 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象

  2. 指针无需在定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

获取对象的地址

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

int ival=2;int *p=&ival;   //p存放变量ival的地址,或者说p是指向变量ival的指针

利用指针访问对象(解引用符(操作符*))

如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问该对象

int ival = 2;int *p = &ival;  //p是指向变量ival的指针cout<< *p;       //由符号*得到指针p所指的对象,输出2

对指针解引用会得出所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值

*p = 4;      //由符号*得到指针p所指的对象,即可经由p为变量ival赋值cout<< *p;   //输出4

2.6.3 编写自己的头文件

虽然可以在函数体内定义类,但是这样的类毕竟受到一些限制。所以,类一般都不定义在函数体内。

当在函数体外部定义类时,在各个指定的源文件中可能只有一处该类的定义。而且,如果要在不同文件中使用同一个类,类的定义就必须保持一致。

为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在头文件的名字应该与类的名字一样。

预处理器概述

确保头文件多次包含仍能安全工作的常用技术是预处理器,她由C++语言从C语言继承而来。

预处理器是在编译之前执行的一段程序,可以部分地改变我们所写的程序。

C++程序还会用到的一项预处理功能是头文件保护符

头文件保护符

头文件保护符依赖于预处理变量。

预处理变量有两种状态:已定义和未定义。

#define指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:

#ifdef 当且仅当变量已定义时为真,#ifnde f当且仅当变量未定义时为真

使用这些功能就能有效地防止重复包含的发生

#ifndef SALES_H#define SALES_H#include <string>struct Sales_data{    std::string book;    double i=0;};#endif

第一次包含SALES_H时,#ifndef 的检测结果为真,预处理器将顺序执行后面的操作直至遇到 #endif 为止。

后面如果再一次包含SALES_H时,则 #ifndef 的检测结果为假,编译器将忽略 #ifndef 到 #endif 之间的部分。

3.2 标准库类型string

注:
在C++、Java、Python等编程语言中,string就是字符串类型。

而在C语言中,字符串用字符数组表示,没有单独的string类型变量。可通过string.h中定义的函数完成字符串的相关操作。

标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件。

例如:

#include<string>using std::string;

3.2.1 定义和初始化string对象

下面是几个初始化string对象最常用的方式:

string s1;          //默认初始化,s1是一个空字符串string s2=s1;       //s2是s1的副本string s3="hi";     //s3是该字符串字面值的副本string s4(10,'c');  //s4的内容是cccccccccc(10个c)

直接初始化()和拷贝初始化(=)

C++语言有两种不同的初始化方式

  1. 拷贝初始化:
    使用等号(=)初始化一个变量,编译器把等号右侧的初始值拷贝到新创建的对象中去

  2. 直接初始化
    如果不适用等号,则执行的是直接初始化(C++语言独有的初始化方式,Java,Python,C,都没有)

比较直接初始化和拷贝初始化:

当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果像下面那样初始化时要用到的值有多个,一般使用直接初始化的方式:

string s4(10,'c');  //s4的内容是cccccccccc(10个c)

对于要用多个值进行初始化的情况,非要用拷贝初始化的方式来处理也不是不可以,不过需要显式的创建一个临时的对象用于拷贝:

string s5=string(10,'c');

这条语句本质上等价于下面的两条语句;

string temp(10,'c');string s5=temp;

其实我们可以看到,尽管初始化s5的语句合法,但和初始化s4相比,可读性较差。

3.3 标准库类型vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象

因为vector“容纳着”其他的对象,所以它常被称为容器

要想使用vector,必须包含适当的头文件。

include<vector>using std::vector;

3.3.1 定义和初始化vector对象

初始化vector对象的方法

vector<T> v1;           //v1是一个空vector,它潜在的元素是T类型的,执行默认初始化vector<T> v2(v1);       //v2中包含有v1所有元素的副本vector<T> v2=v1;        //等价于vector<T> v2(v1)vector<T> v3(n,val);    //v3包含了n个重复的元素,每个元素的值都是valvector<T> v4(n);        //v4中包含了n个重复执行值初始化的对象vector<T> v5{a,b,c...}; //v5中包含了初始值个数的元素,每个元素被赋予了响应的初始值vector<T> v5={a,b,c...};//等价于vector<T> v5{a,b,c...}

列表初始值还是元素数量?

在某些情况下,初始化的真实含义依赖于初始值时用的是花括号(列表初始化)还是圆括号(直接初始化)。

通过使用花括号或圆括号可以区分上述这些含义:

vector<int> v1(10);     //v1有10个元素,每个的值都是0(默认初始化值为0)vector<int> v2{10};     //v2有1个元素,该元素的值是10vector<int> v3(10,1);   //v3有10个元素,每个的值都是1vector<int> v4{10,1};   //v4有两个元素,值分别是10和1

3.3.3 其他vector操作

v.push_back(t)  //向v的尾端添加一个值为t的元素

注意,可以用下标形势访问元素,但不能用下标形式添加元素

3.4 迭代器介绍

我们已经知道可以使用下标运算符来访问string对象的字符或vector对象的元素,还有另外一种更通用的机制也可以实现同样的目的,这就是迭代器

3.4.1 使用迭代器

和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员

0 0
原创粉丝点击