您的位置:首页 > 编程语言 > C语言/C++

《 C++ Primer 第五版》笔记(一):1-3章 变量和基本类型,字符串、向量和数组

2016-09-11 15:10 441 查看

开始

初识输入输出

C++语言并未定义任何输入输出(IO)语句,取而代之,包含了一个全面的标准库来提供 IO 机制(以及很多其他方法)。

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

标准输入输出对象

标准库定义了4个 IO 对象。为了处理输入,我们使用一个名为 cin 的 istream 类型的对象,这个对象也被称为标准输入。对于输出,我们使用一个名为 cout 的 ostream 类型的对象,这个对象被称为标准输出。标准库还定义了其他两个 ostream 对象,名为 cerr 和 clog,我们通常用 cerr 来输出警告和错误消息,clog 用来输出程序运行时的一般性消息。

向流写入数据

使用输出运算符(<<)在标准输出上打印消息,<< 运算符接受两个运算对象,左侧的运算对象必须是一个 cout 对象,右侧的运算对象是要打印的值:

std::cout<< "hello,world!" << std::endl;


std::endl 是一个回车。

从流中读取数据

使用输入运算符(>>)从标准输入中读取数据,>>运算符接受两个运算对象,左侧的运算对象必须是一个 cin 对象,右侧的运算对象是接受的对象:

std::cin>>v1>>v2; //将读入的第一个值存入到v1中,第二个值存入v2中

//与下面两条语句的执行效果一致
std::cin>>v1;
std::cin>>v2;


使用标准库中的名字

之前我们在代码中使用了 std::cout 和 std::endl,而不是直接使用 cout 和 endl。这是因为名字 cout 和 endl是定义在名为 std 的命名空间中的。命名空间可以帮助我们避免不经意的名字定义冲突,标准库定义的所有名字都在命名空间 std 中。

通过命名空间使用标准库有一个副作用:当使用标准库中的名字时,必须显式地说明。例如,需要写出 std::cout ,通过作用域运算符(::)来指出我们想使用定义在命名空间 std 中的名字 cout

变量和基本类型

基本内置类型

C++ 定义了一套包括算术类型和空类型(void)在内的基本数据类型。

算术类型

算术类型分为两类:整型和浮点型,包含了字符、整型数、布尔值和浮点数。C++ 算术类型如下图所示:



解析:

布尔类型(bool)的取值是真(1)或者假(0),所有非零值(正数,负数)都是真,零值则为假。

C++ 提供了几种字符类型,其中多数支持国际化。基本的字符类型是 char,一个 char 的空间确保可以存放 机器基本字符集中的任意字符 对应的数字值。其他字符类型用于扩展字符集。wchar_t 类型用于确保可以存放 机器最大扩展字符集中的任意一个字符,类型 char16_tchar32_t 则为 Unicode 字符集服务。

除字符和布尔类型之外,其他整型用于表示不同尺寸的整数。C++ 语言规定,一个 int 至少和一个 short 一样大,一个 long 至少和一个 int 一样大,一个 long long 至少和一个 long 一样大。其中,数据类型 long long 是在 C++11中新定义的。

带符号类型和无符号类型

除了布尔型和扩展的字符型之外,其他整型可以划分为带符号的和无符号的。带符号类型可以表示正数、负数或 0,无符号类型仅能表示大于等于0的数(即没有了负数)。

无符号类型中所有比特都用来存储值,没有符号位。

类型转换

程序会自动进行类型转换。

例子:

bool b=42; //b为真,即1
int i=b;   //i的值为1
i=3.14     //pi的值为3.0
double pi=i; //pi的值为3.0
unsigned char c1=-1; //假设char占8比特,c1的值为255
signed char c2=256;  //假设char占8比特,c2的值是未定义的


类型所能表示的值得范围决定了转换的过程:

把非布尔类型的算术值赋给布尔类型时,初始值为0,则结果为false,否则结果为true

把布尔值赋给非布尔类型时,初始值为false,则结果为0,初始值为true,则结果为1

把浮点数赋给整数类型时,结果值仅保留浮点数中小数点之间的部分

当整数值赋给浮点类型时,小数部分记为0

当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8比特大小的 unsigned char 可以表示0至255区间的值,如果我们赋了一个区间以外的值,则实际的结果是该值对256取模后所得的余数。

当赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作、可能崩溃、也可能生成垃圾数据。

变量

变量定义

变量定义的基本形式是:类型说明符,随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。列表中每个变量名的类型都由类型说明符指定,定义时可以为一个或多个变量赋初值。

在 C++语言中,当对象在创建时用等号=获得了一个特定的值,我们说这个对象被初始化了。

在C++ 语言中,初始化使用等号=来完成的,初始化不是赋值,初始化的含义是给变量赋予一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。

列表初始化(C++11 新特性)

C++ 语言定义了初始化的好几种不同形式,这也是初始化问题复杂性的一个体现。例如,要想定义一个名为 i 的 int 变量并初始化为 0,以下的语句都可以做到:

int i=0;
int i={0};
int i{0};
int i(0);


用花括号来初始化变量的形式称为列表初始化。

变量声明和定义的关系

为了允许把程序拆分成多个逻辑部分来编写,C++ 语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。

为了支持分离式编译,C++ 语言将声明和定义区分开来。声明使得变量名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建与名字关联的实体。

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

extern int i; //声明i而并非定义i
int j; //定义j


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

标识符

C++ 的标识符由字母、数字、下划线组成,其中必须以字母或下划线开头。标识符的长度没有限制,但是对大小写敏感。标识符不能和关键字和操作符相同



复合类型

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

引用

C++11 中新增了一种引用:所谓的“右值引用”,将在13.6.1节中做介绍。当我们使用术语“引用”时,指的其实是“左值引用”。

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

通过将声明符写成 &d 的形势来定义引用类型,其中 d 是声明的变量名,每个引用标识符都必须以符号 &开头:

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


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

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

指针

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

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

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

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

定义指针类型的方法是将声明符写成 d 的形式,每个指针变量前都必须有符号 :

int *ip1,*ip2; //ip1和ip2都是指向int对象的指针
double dp,*dp2; //dp是double型对象,dp2是指向double对象的指针


获取对象的地址

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

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


限定符

有时我们希望定义这样一种 变量 ,它的值不能被改变。可以用关键字 const 对变量的类型加以限定:

const int bufSize=512; //缓冲区大小


这样就把 bufSize 定义成了一个常量,任何试图为bufSize 赋值的行为都会报错:

bufSize=22; //报错:试图向const对象写值


因为const 对象一旦创建后其值就不能再改变,所以const 对象必须初始化。初始值可以是任意复杂的表达式。

处理类型

类型别名

类型别名是一个名字,它是某种类型的同义词。使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用。

有两种方法可用于定义类型别名。传统的方法是使用关键字 typedef

typedef double wages;  //wages 是double的同义词
typedef wages base,*p; //base是double的同义词,p是double* 的同义词


C++11新标准规定了一种新的方法,使用别名声明来定义类型的别名:

using SI=Sales_item;//SI是Sales_item的同义词


auto 类型说明符

C++11新标准引入了auto 类型说明符,用它就能让编译器替我们去分析表达式所属的类型。auto定义的变量必须有初始值

auto i=0,*p=&i;      //正确,i是整数,p是整型指针
auto sz=0,pi=3.14;   //错误,sz和pi的类型不一致


decltype 类型指示符

C++11新标准引入了类型说明符 decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

decltype(f()) sum=x; //sum的类型就是函数f的返回类型


编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型。

自定义数据结构

编写自己的头文件

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

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

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

预处理器概述

确保头文件多次包含仍能安全工作的常用技术是预处理器,它由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 之间的部分。

字符串、向量和数组

命名空间的 using 声明

目前为止,我们用到的库函数基本上都属于命名空间 std,而程序也显示地将这一点标示了出来。例如,std::cin 标示从标准输入中读取内容,此处使用作用域操作符(::)的含义是:编译器应该从操作符左侧名字所示的作用域中寻找右侧那个名字

本章将学习使用 using 说明,来简化这个操作。有了 using 说明就无需专门的前缀(形如命名空间 ::),也能使用所需的名字了。using 声明具有如下的形式

using namespace::name;


每个名字都需要独立的using 声明

按照规定,每个using 声明引入命名空间中的一个成员

using std::cin;
using std::cout; using std::end;


标准库类型string

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

例如:

#include<string>
using std::string;


定义和初始化string对象

下面是初始化 string对象 的方式:

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


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

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

拷贝初始化:

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

直接初始化

如果不适用等号,则执行的是直接初始化

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

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

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


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

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


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

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


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

string 对象上的操作



读写 string 对象

string s;
cin>>s;
cout<<s<<endl;


在执行读取操作时,string 对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇见下一处空白为止。

如果程序的输入是“ Hello World! ”,则输出将是“Hello”。

读取未知数量的string对象

string word;
while(cin>>word)
cout<<word<<endl;


在该程序中,读取的对象是string,如果没有遇到文件结束标志或非法输入,那么将一直执行循环。

使用 getline 读取一整行

getline 函数的参数是一个输入流和一个 string 对象,函数从给定的输入流中读入内容,直到遇到换行符为止,然后把所读的内容存入到那个string 对象中(注意不存换行符)。

如果输入一开始就是换行符,那么得到的结果是空 string。

string line;
while(getline(cin,line))
cout<<line<<endl;


处理 string 对象中的字符

在 cctype 头文件中定义了一组标准库函数处理这部分工作。



处理每个字符,基于范围的 for 语句

如果想对 string 对象中的每个字符做操作,最好的使用 C++11 新标准提供的:范围 for 语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,语法为:

for(declaration: expression)
statement


expression 是一个对象,表示一个序列,declartion 部分负责定义一个变量,该变量被用于访问序列中的基础元素。每次迭代,declartion 部分的变量会被初始化为 expression 部分的下一个元素值。下面有个例子:

string str("some string");
for(auto c:str)     //对于str 中的每个字符
cout<<c<<endl;  //输出当前字符,后面紧跟一个换行符


使用范围 for 语句改变字符串中的字符

假设我们想要把字符串改写成大写字母的形式:

string s("Hello World!!");
for(auto &c:s)
c=topper(c); //c是一个引用,因此赋值语句将改变s中字符的值
cout<<s<<endl;


上述代码中输出结果是:

HELLO WORLED!!


只处理一部分字符?

下标运算符([ ])接受的输入参数是 string::size_type 类型的值,这个参数表示要访问的字符的位置,返回值是该位置上字符的引用。

string 对象的下表必须大于等于0而小于 s.size()。

if(!s.empty())
cout<<s[0]<<endl; //输出s的第一个字符


标准库类型vector

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

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

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

include<vector>
using std::vector;


定义和初始化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个重复的元素,每个元素的值都是val

vector<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个元素,该元素的值是10

vector<int> v3(10,1);   //v3有10个元素,每个的值都是1

vector<int> v4{10,1};   //v4有两个元素,值分别是10和1


其他vector操作



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


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

迭代器介绍

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

使用迭代器

和指针不一样的是,获取迭代器不是使用取地址符,而是有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为 begin 和 end 的成员,其中 begin 成员负责返回指向第一个元素(或第一个字符)的迭代器,end 成员返回的是尾后迭代器。

迭代器运算符



迭代器运算



数组

定义和初始化内置数组

数组是一种复合类型,数组的声明形如 a[d],其中 a 是数组的名字,d 是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。也就是说,维度必须是一个常量表达式。

访问数组元素

与标准库类型 vector 和 string 一样,数组的元素也能使用范围 for 语句或下标运算符来访问。

指针和数组

在C++ 语言中,指针和数组有非常紧密的联系。使用数组的时候,编译器一般会把它转换为指针。

在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。

指针也可以作为迭代器

int arr[]={0,1,2,3,4,5,6,7,8,9};
int *p=arr; //p指向arr的第一个元素,即arr[0]
++p; //p指向arr[1]


C 风格字符串

C 风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符结束(’\0’)。

C 标准库 String 函数



多维数组

多维数组其实是数组的数组。

当一个数组的元素仍然是数组时,通常使用两个维度来定义它:一个维度表示数组本身的大小,另外一个维度表示其元素(也是数组)的大小。

int ia[3][4]={0}; //大小为3的数组,每个元素是含有4个整数的数组,将所有元素初始化为0


对于二维数组来说,第一维度称作行,第二维度称作列。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: