C++进阶-2

2.数据类型

引言:在第二章数据类型中,只对数据类型进行简单介绍,剩下的会在下一章中对其进行补充。

2.1基本整型

计算机内存的基本单位是位(bit),由0和1组成。若是8位的内存,则可以设置256种组合,可以用来表示数值,其范围是0~255。

字节(byte)通常指的是8位的内存单元。1KB = 1024byte,1MB = 1024KB。C++中定义一个字节至少能容纳一个基本字符集中的字符。在美国,基本字符集通常是ASCⅡ和EBCDIC,它们都可以用8位来容纳。而国际编程需要使用更大的字符集,如Unicode,因此需要使用16位或32位的字节。为了统一,后面默认一个字节是8位。

根据编译器的不同,类型的宽度也不同。在C++中提供了一个标准:short ≤ int ≤ long ≤ long long;short ≥ 16bit,long ≥ 32bit,long long ≥ 64bit。在64位的windows系统中,查看C:\Program Files (x86)\Microsoft Visual Studio 2017\2017\Community\VC\Tools\MSVC\14.14.26428\include目录下limits.h文件如下:

C++进阶-2_第1张图片

2-1

为后面的代码统一类型宽度,以图2-1为标准。short为16位,int为32位,long为32位,long long为64位。

基本整型的类型都明白了,该怎么使用呢?有3种方式:

1.变量声明和赋值:

int a;

a = 10;

2.变量初始化(等价于声明和赋值)

int a = 10;

3.C++11初始化方法:

int a = {10}; //初始化10

int a{10}; //初始化10

int a = {}; //初始化0

int a{}; //初始化0

前面所说的4种基本整型都是有符号整型,而它们都有各自的无符号整型(非负)。unsigned short、unsigned int(或unsigned)、unsigned long、unsigned long long是无符号整型。如short范围是-32768~32767,unsigned short的范围是0~65535。

整型的字面值可以表示为八进制、十进制、十六进制。八进制是以0开头的整数,十六进制是以0x开头的整数,若表示20,则:

20 //十进制

024 //八进制

0x14 //十六进制

2.2浮点类型

C++有3种浮点类型:float、double、long double。float的有效位数大于等于6,数值宽度大于等于32位;double的有效位数大于等于10,数值宽度大于等于48位;long double的有效位数大于等于10,数值宽度大于等于double的位数。通常,float有7个有效位数,32位宽度;double有16个有效位数,64位宽度;long double有96或128位宽度,有效位数不定。

对于浮点常量通常默认为double型,若想表示为float型,则使用f、F为后缀;若想表示为long double型,则使用l、L为后缀。

1.234    // double

1.234f   // float

1.234L   //long double

浮点类型的使用和基本整型一样,不在多述。

2.3布尔类型

bool类型的取值是真(true)和假(false)。bool类型的使用和基本整型、浮点类型相同,共有3种方式,不在多述。

当把非bool类型的数值赋给bool类型时,数值为0则结果为false,数值非0则结果为true;当bool类型的数值赋给非bool类型时,false则结果是0,true则结果是1。

bool a = 10.1; //a = 1,即a为真

int b = a; //b = 1

2.4字符

基本的字符类型是char,根据前面字节的定义,我们知道一个char的大小和一个机器字节一样,因此char的位数为8 bite。

int不同,char在默认情况下,既不是没有符号,也不是有符号。因此,字符类型被分为3种:char、signed char、unsigned char。如果将char用作数值类型,则signed char表示范围是-128~127,unsigned char表示范围是0~255。

除了3种基本字符类型(用于基本字符集),还有其他的字符类型用于扩展字符集,如wchar_t、char16_t、char32_t。wchar_t用于确保可以存放机器最大扩展字符集中的任意一个字符;char16_t、char32_t是C++11添加的,专门为Unicode字符集服务。

注:若程序处理的字符集无法用8位的字节来表示,则C++处理方式有2种:1.若基本字符集是大型字符集,编译器将char宽度定义为16位或更长的位数(字节的位数也改变为16或其他)。2.若同时支持一个小型的基本字符集和大型的扩展字符集,8位用char表示,16位用wchar_t表示。

char的3种使用方式:

1.声明+赋值

char cr;

cr = ’A’;

cout<<”cr = ”<

2.初始化

char cr = ’A’;

3.char转换为int

char cr = ’A’;

int A = cr; // A = 65

wchar_t类型可以通过加前缀L来表示宽字符常量或宽字符串常量;其输入输出使用wcout、wcin。2种使用方式:

1.声明+赋值

wchar_t wct;

wct = L’A’;

wcout<<”wct = ”<

2.初始化

wchar_t wct = L’A’;

char16_t、char32_t都是无符号类型,宽度分别是16位和32位。char16_t前缀加u表示char16_t型字符常量或字符串常量;char32_t前缀加U表示char32_t型字符常量或字符串常量。

2.5枚举

枚举的作用是定义新的类型,用新类型来创建符号常量。共2种使用方式:

1.默认数值

enum colour {red, orange, yellow, green, blue};  //这些符号不要用双引号括起来

colour cor;

cor = orange;

cout << "cor = " << cor << endl; //打印 cor = 1

代码解释:

首先定义了枚举类型colour,此类型中的符号常量分别是red...blue,分别对应数值0~4。再定义colour枚举的变量cor,其取值范围只能是red...blue。

枚举类型可以向int型转换:

int tinct = yellow; //tinct = 2

可以省略枚举类型,直接使用符号常量:

enum {red, orange, yellow, green, blue};

int tinct = 2;

if (tinct = yellow)

{

cout << "tinct = " << yellow << endl; //打印 tinct = 2

}

2.非默认数值

enum colour {red, orange, yellow = 100, green, blue};

代码解释:

可以部分赋值,也可以全部赋值。上面采用了部分赋值,即red = 0,orange = 1,yellow = 100,green = 101,blue = 102。

其它使用都和默认数值一样,不再多述。

2.6同类数据的存储

2.6.1数组

若需要存储多个同类型的数据,则可以使用数组。数组[]内的值表示数组元素的总数,但是数组元素的下标是从0开始的。数组的3种使用方式:

1.声明+赋值

int abc[5];

abc[0] = 10;

abc[3] = 11;

abc[4] = 10;

在声明(类型为int数组)后进行赋值,不可以一次性赋多个值,只能一个一个赋值,并且未赋值的数组元素会是一个随机值(不是0)。数组声明必须给定数组元素的总数。因为这是静态数组,属于静态联编,在编译时就已经确定数组大小。

注:数组不能整体赋值,数组声明或初始化时必须指定元素总数。

2.初始化

int abcd[5] = {12,13,14};

未初始化的元素,编译器会自动设置为0.如abc[3]、abc[4]为0。

int abcd[] = {12,13,14};

若数组在初始化是未给定元素总数,则C++编译器自动计算数值,即abc元素总数为3,相当于int abcd[3] = {12,13,14}。

3.C++11初始化

int abcde[5] {12,13,14}; //去掉 =

int abcde[5] = {}; //初始化为0

int abcde[5] {}; //初始化为0

2.6.2模板类vector

前面创建的数组都属于静态数组,若要创建动态数组,该怎么办?在C++的头文件vector中,通过模板类vector来实现动态数组,模板类vector包含在名称空间std内。模板类vector有2种使用方式:

1.模板类vector对象的元素长度未知

#include 

#include

int main(void)

{

using namespace std;

vector<int> vi;

vi.push_back(1);

cout << "vi[0] = " << vi[0] << endl;  //打印 1

}

vi是类vector<int>的对象,它可以当成一个int型动态数组。vi的初始长度设置为0,若要调整vi数组长度,如进行插入元素或删除元素等操作,需要调用模板类vector中的成员函数。

2.模板类vector对象的元素长度已知

#include 

#include

int main(void)

{

using namespace std;

vector<int> vi(1);

vi[0] = 1;

vi.push_back(2);

cout << "vi[0] = " << vi[0] << endl; // 打印vi[0] = 1

cout <<" vi[1] = " << vi[1] << endl; // 打印vi[1] = 2

}

vi是类vector<int>的对象,在声明时确定只有1个int元素,因此可以使用vi[0] = 1但若是要增加vi长度,则只能使用vi的成员函数push_back()。

4种方式来初始化模板类vector对象(长度已知)

vector<int> via(8);   //初始化via,其可以包含8个int元素

vector<int> vib(8,0); //初始化vib,其包含8个0

vector<int> vic(vib); //初始化vic,其元素和vib相同,深拷贝

vector<int> vid(vic.begin(),vic.begin()+4);//初始化vid,其包含vic的第1-4元素,深拷贝

2.6.3模板类array

若需要存放一组长度固定的元素,可以采用数组。但是数组的使用并不安全,数组没有边界检查,你在使用的过程中可能会造成数组越界(int a[10];a[20] = 0;不会报错)。因此C++11新增了模板类array(头文件array),其和vector一样包含在名称空间std中,但模板类array和模板类vector不同,其不能添加或删除元素来改变长度。

模板类array和数组都是存放固定长度的同类数据,其不同之处:

1.模板类array可以一次赋多个值或者直接把另一个模板类array(元素类型和长度必须相同)直接赋给它;数组一次只能赋一个值且不能整体数组赋值。

2.数组越界,运行时没有问题;模板类array越界,运行会中止。

#include 

#include

int main(void)

{

using namespace std;

int i[4] = { 1,2,3,4 };

array<int, 4> ai; //声明

ai = { 1,2,3,4 }; //赋值

//或者直接初始化array ai = { 1,2,3,4 };

cout << "ai[0] = " << ai[0] << endl;

ai[1] = 10; //修改元素

cout << "ai[1] = " << ai[1] << endl;    // 打印ai[1] =10

cout << "ai[1] = " << ai.at(1) << endl; // 打印ai[1] =10

//cout << "i[5] = " << i[5] << endl;  可以运行

//cout << "ai[5] = " << ai[5]<< endl; 运行中止

//cout << "ai[5] = " << ai.at(5) << endl;  运行中止,抛出out of range异常

array<int, 4>::iterator aiir;

for (aiir = ai.begin(); aiir != ai.end(); aiir++) //迭代器的使用

cout << *aiir << endl;

return 0;

}

2.7字符串

C++处理字符串有2种方式,1.来自C语言的C-风格字符串;2.基于string类库的字符串。

2.7.1 C-风格字符串

C-风格字符串以空字符结尾,即‘\0’。因此用数组表示固定长度字符串时,其元素总数要加一。C-风格字符串的使用方式有3种:

1.数组表示固定长度字符串

char cr[5] = {'c','h','a','r'};//不可以char cr[5];cr[5] = { 'c','h','a','r'};

char cr[5] = char;  //不可以char cr[5]; cr[5] = char;

字符串数组不可以整体赋值,可以整体初始化;并且以这种方式表示字符串,其数组长度必须比元素总数多1。当数组内存中存放完字符串,还剩下的元素空间自动补空字符‘\0’。

#include 

int main(void)

{

using namespace std;

const char cr[5] = {'c','h','a','r'}; //const表示只读

for (int i = 0; i < 5; i++)

cout << cr[i] << endl; //或cout << cr<< endl; 

char str[8] = "string";

for (int i = 0; i <8; i++)

cout << str[i] << endl;

str[0] = 'c';

cout <<"str[0] = "<< str[0] << endl;

return 0;

}

注:若这样定义char cr[4] = {'c','h','a','r'}在字符串读取时,读完‘r’后会继续读,直到遇到‘\0’为止。

2指针表示固定长度字符串

const char * name = "zhangsan"; //初始化

const char * name;  //声明

name = "zhangsan";  //赋值

这表示cr指向一个长度为5的char型数组,cr的值表示数组第一个元素的地址。使用带双引号的字符串来初始化(或声明+赋值)字符指针时,必须加const表示字符串只读。若不想用const,可以使用字符数组来初始化字符指针:

char name[20] = "zhangsan";

char * cr;

cr = name;

cout << name << endl << cr << endl;

总结:当字符串只读时,可以使用双引号的方式来初始化字符指针;当字符串可写时,需要使用字符数组来初始化字符指针。

注:数组可以使用cin来输入字符串,而指针不能,指针需要初始化才能用cin(数组的声明和指针不同,数组在声明时就会开辟内存,而指针声明后会随机一个垃圾值,此时读写指针太危险,编译器不会通过):

char  cr[20];

cin >> cr; //可以

而:

char  *cr;

cin >> cr;//不可以

3指针表示未知长度字符串

动态开辟内存,使用new和delete来分配释放内存。

#include 

#include   //字符串库函数的头文件

int main(void)

{

using namespace std;

int n;

cout << "please input the number of your name:";

cin >> n;

char * cr = new char[n + 1];//在堆空间中开辟可以存放n+1个char类型的内存char数组

char  name[20];

cout << "please input  your name:";

cin >> name;

strcpy_s(cr, n+1, name);//strcpy不安全,strcpy_s计算空字符

cout << "your name is "<< cr << endl;

delete []cr; //释放char数组

return 0;

}

2.7.2 string类

#include 

#include   //string类的头文件

int main(void)

{

using namespace std;

string str1 = "zhangsan";

string str2;

const char * cr= "lisi";

str2 = cr;

string str3;

char ar[20] = "wanger";

str3 = ar;

cout << str1 << endl << str2 << endl << str3 << endl;

str1 = "zhangsan2";

str2 = "lisi2";

str3 = "wanger2";

cout << str1 << endl << str2 << endl << str3 << endl;

cout << cr << endl ;//打印:lisi

return 0;

}

代码解释:

代码中使用3种方式来初始化string类的对象,直接使用双引号字符串初始化;使用字符指针初始化;使用字符数组初始化。

为什么cr的类型是const char *,赋值给str2后,str2可以修改?在C++中拷贝分为2种方式:浅拷贝和深拷贝。以拷贝字符串为例,浅拷贝就是将a字符串的首地址赋给b,b通过字符串首地址来对a进行读写,其实它们操作的是同一块内存的字符串。深拷贝就是b重新开辟一块内存来存放和a相同的字符串,它们操作的是两块不同内存的字符串。

string类的成员函数中,有=运算符重载函数,其对传入的字符串进行处理,即使用new来开辟空间。

2.8指针

有数据就有数据存放的地址,在C/C++使用指针来访问数据的地址(一个地址可以存放二进制数据的位数,称为位宽,它需要根据芯片的数据线总数和其连接方式来确定,这里不讨论)。

2.8.1 指针和基本整型

int count =3;

int * pt; //声明

pt = &count; //赋值

int count =3;

int * pt = &count; //初始化

pt是int型指针,即类型为int *;其指向一个int数据count,即pt的数值是count的地址;*pt是count的值,即*pt = count。

2.8.2指针和数组

#include 

int main(void)

{

using namespace std;

int ia[5] = { 1,2,3,4,5 };

int * ipt;

ipt = ia;

char ca[5] = "char";

char * cpt;

cpt = ca;

cout << cpt << endl;    

for (int i = 0; i < 5; i++)

cout << cpt[i];

cout << endl;

for (int i = 0; i < 5; i++)

cout << *(cpt+i);

cout << endl;

cout << ipt << endl;    

cout << &ia[0] << endl;

for (int i = 0; i < 5; i++)

cout << ipt[i] << ' ';

cout << endl;

for (int i = 0; i < 5; i++)

cout <<*(ipt+i)<< ' ';

return 0;

}

代码运行:

 C++进阶-2_第2张图片

为什么打印cpt就可以把字符串打印出来,而打印ipt只打出了数组第一个元素的地址?在前面我们使用char数组和char*来初始化数组,而数组名本身就代表数组存放的地址(第一个元素的地址),因此可以得知字符串的本质类型是char *。当我们打印cpt时,cpt的数据类型是char *,所以打印的其实是以cpt为起点的字符串,直到遇到空字符为止。

用指针来表示数组元素,通常有2个方法,即ipt[i]和*(ipt+i)。

2.8.3指针的危险

1.空指针

当指针不指向任何一个地址,这样的指针称为空指针。若pt是一个指针:

pt = 0;

pt = ‘\0’;

pt = NULL;

这些都可以表示pt是一个空指针。

C++11中,用nullptr来表示空指针,这也是C++11的新标准。

在使用new/malloc时,若分配内存失败会返回一个空指针,若不进行判断指针,则在后面使用这个指针时会出现很大的问题。

解决方案:若返回值是指针,则if(p!=NULL)...。

2.指针声明

指针声明后,会随机给一个垃圾值,而不是空指针,此时读写指针非常危险,因此在定义指针时尽量初始化,哪怕是nullptr也好。

#include 

int main(void)

{

using namespace std;

int * pt = nullptr;

int count = 5;

pt = &count;

cout << *pt << endl;

return 0;

}

3. 内存泄漏

程序运行需要内存,内存是计算机有限且宝贵的资源。在C/C++中使用malloc/new来申请内存,通过动态分配得到堆空间上的内存,当我们使用完后需要及时手动释放(栈上的内存在使用完后,系统会自动收回)。若不释放,会造成内存占用率越来越高,严重会导致进程崩溃,这就是内存泄漏。

解决方案:malloc/free, new/delete成对使用。

4.野指针

野指针也称悬挂指针,野指针不是空指针。

在程序中会采用多指针指向同一块堆内存(堆空间由new/malloc开辟,而局部变量存放在栈空间,随函数结束会自动销毁),但这会带来问题:

其中一个指针delete后,其他指针无法判断是否有效。解决方案:谁申请,谁释放。

当指针释放后,需要把指针置为nullptr(空指针),因为指针释放只是释放那一块堆内存,而指针的值还是那一块内存的值。并且还需要把其他指向这块内存的指针置为nullptr。解决方案:维护一个指针计数,当多一个指针指向这块内存,计数加一;一个指针销毁或者不指向这块内存或置为nullptr,计数减一;当计数为0时,释放内存(智能指针)。

2.9引用

引用:为对象起另外一个名字,即别名。

#include 

int main(void)

{

using namespace std;

int val = 20;

int &ival = val;

val++;

cout << ival << endl; //打印21

ival++;

cout << val << endl; //打印22

return 0;

}

代码解释:

初始化val为20,类型为int引用的ival对val进行引用,因此ival就是val的别名,它们操作的是同一块内存。val变化,ival也变化;ival变化,val也变化。

注:引用必须初始化,不可以int &ival;ival = val。

2.10不同类型数据的存储

对于相同类型的数据,我们可以使用数组、模板类vector、模板类array来存储,而对于不同数据类型的数据改怎么样存储呢?

2.10.1结构体

通过定义一种新的数据类型,可以存储多种类型的数据,这种类型称为结构体,它是由用户来定义的。

#include 

#include 

//不能使用primary school,必须用_连接

enum grade { primary_school, middle_school, collge };

//在结构体student的定义中,每一项都要用“;”来分割,包括最后一项

struct student { 

//必须使用std::string,因为在结构体student定义之前未声明名称空间的使用

std::string name;  

int age;

grade gra;

};  //结构体定义后面必须有分号

int main(void)

{

using namespace std;

/*在变量stu1(类型是student结构体)的初始化中,每一项用“,”隔开,不包括最后一项(同数组、枚举)*/

student stu1 = {   

"zhangsan",  //称为变量stu1的name成员

10,

primary_school

};

cout << stu1.name << endl<<stu1.gra<< endl; //枚举值得打印只能打印出数字

switch (stu1.gra) {        //采用switch来打印枚举值

case 0:printf("primary_school\n");

break;

case 1:printf("middle_school\n");

break;

case 2:printf("collge\n");

break;

default:printf("none\n");

}

return 0;

}

代码解释:

要使用结构体,必须先定义结构体,在main函数的代码上面定义了结构体student,在这里新的数据类型是结构体student;在结构体student中定义的name、age、gra称为结构体student的成员;在main函数中的stu1是结构体student的变量。

除了整体对student的变量stu1初始化外,还可以分别来初始化stu1的成员:

student stu1;

stu1.age = 10;

stu1.gra = primary_school;

stu1.name = "zhangsan"; 

至于变量的声明还可以在结构体定义的后面来声明:

struct student { 

std::string name;  

int age;

grade gra;

}stu1,stu2;

在结构体定义后面直接初始化也可以:

struct student { 

std::string name;  

int age;

grade gra;

}stu1 =

{   

"zhangsan",  

10,

primary_school

};

2.10.2共用体

共用体类似于结构体,在定义中可以存储不同的数据类型,但其变量只能存储其中一种数据类型。

#include 

#include 

using namespace std;

union student {

int age;

const char *name; //不可以用string类;用char数组,不可以整体赋值

}stu1;

int main(void)

{

stu1.age = 10;

cout << stu1.age << endl;

stu1.name = "zhangsan";

cout << stu1.name << endl;

return 0;

}

共用体student的变量为stu1,其中定义了2个成员,分别是age、name。在stu1成员的赋值中,当赋值给age,则name是空的;赋值给name,则age是空的。也就是说age和name公用一段内存,其大小是最大成员的长度。

2.11类

面向对象编程有三大特性:封装性、继承性、多态性。为了实现这3大特性,C++对结构体进行改进,增加了一种新的数据类型:类。

同类数据的存储使用数组,不同类型数据的存储使用结构体。当我们把不同类型的数据和对数据的操作(函数)集成在一起,于是就出现了类。

#include 

#include 

using namespace std;

class Student {

public:

const char* name;

string curriculum;  //课程

int grade;         //成绩

 

public:

void printinfo(void)

{

cout << "name = " << name << " curriculun = " << curriculum << " grade = " << grade << endl;

}

};

int main(void)

{

Student stu1;

stu1 = { "zhangsan","English",80 };

stu1.printinfo();

return 0;

}

类的定义和结构体一样,需要在尾部加上分号。同时,为了约定书写习惯,在后面的类定义中,将第一个字母大写,即Student。

在这里不再对类进行多介绍,在后面的章节中,会根据对象化编程的3大特性来对类进行分析。

你可能感兴趣的:(C++进阶-2)