跟着EP学C++(4th) 变量与数据类型

本篇内容从”数据类型”开始,以后内容为选修部分。它们是有难度的,你可以快速地扫一眼,尽可能地学习。但它们是有用的。在当我们开始面向对象的内容的时候,那时之前的所有选修部分就必须要学完了——到那时学这些的难度已经不大了。
By EnderPearl


顺便提一句,从本篇开始,在末尾可能会有一些练习题


添加新的文件

通过VSCode在打开的文件夹中新建文件非常方便。打开VSCode内置的资源管理器(在屏幕左侧,打开快捷键Ctrl+Shift+E),将鼠标移至资源管理器上,点击”新建文件”的图标,会提示你输入文件名(“新建文件”右侧的就是新建文件夹)
这里写图片描述
新建文件add.cpp,开始这一篇的学习吧!

//add.cpp
#include<iostream>
using namespace std;
int main(){
    int a,b;
    cin>>a>>b;
    cout<<a+b<<endl;
    return 0;
}

输入编译命令:

g++ add.cpp -o add

这里写图片描述
(注:红框框框起来的是输入)

通过cin输入

用cin输入就像用cout输出一样简单:

int a;
string str; //string是字符串
cin>>a;
cin>>str>>a; //cin的>>也可以连续使用

但事实上,下面的内容才是核心:

变量

(注:变量其实是C语言的概念,在C++中,它有多种称呼:你可以简单的叫它变量,以面向对象的角度来看也可以叫对象)
变量是程序进行计算的一个非常重要的东西。它可以储存一些东西——因为它在内存中占用一定的空间。
定义变量的方法很简单:

type v1;
type v2,v3; //你也可以一次定义多个变量
type v4=value; //还可以有初始值

例如:

int a;
string str1="Hello,",str2="world!";

只是,你要定义一个变量,你必须选择一个合法的不重复的标识符
标识符规则:

  • 标识符只能由字母(包括大小写),数字和下划线’_’构成
  • 标识符第一个字符不能是数字
  • 标识符不能与C++关键字相同
  • C++标识符区分大小写(即my,My,mY,MY四个是不同的)

当然,你要定义变量,你就离不开……

数据类型

基本数据类型

(注:本文以下内容为选修内容)
C++的数据类型类型分为基本数据类型和复合数据类型
基本数据类型分类如下:

  • 整型
    • 符号整型
      • signed char
      • short
      • int
      • long
      • long long
    • 无符号整型
      • unsigned char
      • unsigned short
      • unsigned(即unsigned int)
      • unsigned long
      • unsigned long long
    • 字符型
      • char
      • wchar_t
      • char16_t(C++11)
      • char32_t(C++11)
    • 布尔型
      • bool
  • 浮点型
    • float
    • double
    • long double

其实关于这些类型并没有什么好说的。要真的认认真真地将可以讲一两个小时。但事实上,很多东西没有必要讲。接下来只讲几条比较重要的:

  1. 符号整型可以存储(一定范围的)正数,负数和0,而无符号整型只能存储非负整数
  2. bool类型只有两个值:true和false(真和假)。bool可以自动提升为int,其中true变成1,false变成0。int可以转换为bool,非零值变成true,0变成false
  3. 浮点型可以存储实数
  4. 整型默认类型是int,字符型默认类型是char,浮点型默认类型是double
  5. 关于各个基本数据类型的大小,范围,以及其它数据,由于它是由编译器决定的,所以你可以去搜索头文件climits和cfloat
  6. 如果你需要使用定长的整型(例如32位的,64位的),可以搜索头文件cstdint
  7. 以上所有类型的每一个词都是关键字
  8. 对于所有数据类型(包括复合数据类型)都有其const版本——在其前面加上const即可。const变量(有时也称常量)一经定义不可修改
  9. auto自动类型推定(C++11)
    在老版本中,auto用来声明这是一个自动变量。从C++11开始,auto用于自动类型推断
    我们还是把它从列表中单独拿出来讲比较好

auto自动类型推定

用auto进行自动类型推断有一个前提条件:有初始值

auto a=5; //int a
auto b=6.0; //double b
auto str="Hello,world!"; //const char *str
auto c; //error:declaration of ‘auto str’ has no initializer

在这里,我们有一个小插曲,就是如何让编译器支持C++11
其实也很简单,只需加上选项-std=c++11即可

g++ add.cpp -o add -std=c++11

引用

(注:C++的引用与Java等语言的引用不太一样。C++的引用又叫别名,它绑定到一个变量上。Java等语言的引用却更像是指针——或者说,句柄——它可以指向不同的对象)
C++定义一个引用很简单:在类型后面加一个&——与指针有些类似

type &ref1=var1;
type var2,&ref2=var2;

例如:

int a;
int &ra=a;
ra=5;
cout<<a<<endl; //a==5
string str="Hello,",&rstr=str;
str+="world!";
cout<<rstr<<endl; //rstr=="Hello,world!";
long &b; //error: ‘b’ declared as reference but not initialized
double &c=0.5; //error: invalid initialization of non-const reference of type ‘double&’ from an rvalue of type ‘double’

引用在定义时就必须说明它引用的变量。从此之后该引用和被引用的变量就是同义词——它们是同一个变量,在内存中的同一处地方
引用不但必须初始化,而且必须引用至一个非const左值

int a,&ra=a;
auto rb=ra; //in fact,rb is an int but not an int&
auto &rc=ra; //rc is a real int&
auto &rd=a; //rd is also an int&

由此可见,auto哪怕初始化值(被引用对象)是一个引用,但事实上它还不是个引用。需要引用可以使用auto&
rb不是引用的根本原因是引用并不是一个数据类型。当你定义完引用ra后,ra其实还是int型的。(这点与指针不同)

复合数据类型

C++有如下的复合数据类型:

  • 数组
  • 结构体
  • 共用体
  • 枚举
  • 指针

在本篇中,我们不会讲类。我们会在之后专门讲面向对象时再来讲这种类型

数组和指针

首先是定义的语法:

type arr1[size1]; //定义数组
type *p1; //定义指针
type arr2[size2],*p2;
type arr3[size3]={value1,value2,...}; //初始化
type var,*p3=&var; //初始化,&在这里是取地址的意思
type arr4[]={value1,value2,...}; //在有初始化列表时可以省略数组长度。编译器会根据元素个数自动选择

例如:

int arr1[10];
int *p1;
int arr2[8]={1,2,3,4,5};
int arr3[]={1,2,3,4,5};

在之前,我们需要一些基础知识的铺垫
那就是,对于内存中的每一个字节,都会有一个地址——就像经纬度可以地球表面一个点一样

//address.cpp
#include<iostream>
using namespace std;
int main(){
    int a;
    cout<<&a<<endl;
    return 0;
}

(注:以后开头有注释并加上文件名的代码,就不需要我来提醒编译了)
在我这里,输出是0xbfd9f178
而且你还可以在编译一次,有可能又不一样了。例如我第二次编译输出的结果是0xbfc69948
如果你看不懂的话……在这里你并不需要囫囵吞枣,这也并不是地址的特殊表示形式。事实上,它就是个整数:

  • 开头的0x表示它是一个十六进制数
  • 后面的部分就是它的值(十六进制)

而指针储存的就是地址。当一个指针储存着一个变量的地址时,就可以通过这个指针获取地址找到这个变量。有时,我们又称这个指针指向这个变量

int a=5;
int *p=&a; //p指向a,&a就是a的地址
cout<<*p<<endl; //*p==5,*p就是p指向的变量

还有一点就是指针运算。指针运算有两种:

  • 偏移运算:p+a或p-a(其中p是指针,a是个整型,表示偏移的长度)
    注意一下,偏移长度的单位并不(一定)是字节。对于type *p;,事实上它的单位是sizeof(type)对于void *p;,确实是以字节为单位的
  • 求差:p1-p2(其中p1和p2都是指针,结果单位同上)

而数组呢?当你定义了一个数组时,它会在内存中开辟一定的空间,并将这段内存空间的首地址存入变量arr中:

int arr[5];

你可以通过arr[1]的方式获取第二个元素,arr[n]等价于*(arr+n)
注:type arr[size]的元素是arr[0]arr[size-1],没有arr[size]!
数组变量arr实质上是指针。但type arr[size];type *p;终归还是有区别的:

//PVsArr.cpp
#include<iostream>
using namespace std;
int main(){
    int arr[5],*p=arr;
    cout<<"sizeof(arr)="<<sizeof(arr)<<endl
        <<"sizeof(p)="<<sizeof(p)<<endl;
    return 0;
}

在我这里输出如下:

sizeof(arr)=20
sizeof(p)=4

原因其实也很简单:sizeof是与类型绑定的。arr的类型是int[5],所以sizeof(arr)==sizeof(int)*5,在我的编译环境中,sizeof(int)==4,所以sizeof(arr)==20。而p是int*型的。在我的编译环境中,sizeof(int*)==4

枚举

枚举定义语法如下:

enum type1{
    const1,const2=value2,...
};
enum type2:type{ //设置底层类型为type(C++11)
    const1,const2=value2,...
};

例如:

enum color{
    red,green,blue
};

//C++11
enum colorex:signed char{
    red,green,blue
}

枚举类型干了这样的事情:

  • 定义枚举类型color
  • 定义color型常量red,green和blue
枚举常量的值

C++标准规定,枚举常量的值如下定义:

  • 对于使用特定值的使用特定值
  • 对于没有给出特定值的选用默认值
    • 第一个常量如果没有给出特定值则默认值为0
    • 其余常量如果没有给出特定值则默认值为上一个数的值+1
  • 可以有值相同的两个常量,而且它们是相等的(即first==second为true)

还有一点,自C++11开始,允许指定底层类型——在类型名后面加上:type即可

作用域内枚举

常规的枚举可能会有些问题,例如:

enum size{
    small,medium,large
};
enum height{
    Short,medium,tall //重复的medium
};

这样会有编译错误:

error: redeclaration of ‘medium’

因为medium在本作用域内已被定义了:(size)medium
我们的方法是使用作用域内枚举(C++11):

//C++11
enum class size{
    small,medium,large
};
enum class height{
    Short,medium,tall
};

作用域内枚举与普通枚举主要有如下差别:

  • 改变常量的作用域。本来small的作用域与size是相同的,作用域内枚举是的small的作用域位size(即必须使用size::small)
  • 禁止隐式转换为整型。即你不能:
    int a=small+large;

结构体与共用体

定义语法:

struct type1{ //结构体
    type11 var11;
    type12 var12,var13;
    ...
};
union type2{ //共用体
    type21 var21;
    type22 var22,var23;
    ...
};

例如:

union id{
    int m_num;
    string m_str;
};
struct book{
    id m_id;
    double m_price;
};
//注:m_前缀是在类中定义成员变量时常用的前缀,至于使用该前缀的原因,我们会在讲定义类的时候说明
...
book b;
b.m_id.m_str="The Old Man and the Sea";
b.m_price=38;
book c;
c=b;

结构体和共用体的主要差别在于,结构体可以将它所有的成员同时存下来,结构体所占的内存空间为(可能略大于)所有成员大小之和;而共用体只用来一次存储一个成员,共用体所占内存大小与空间最大的成员的size相同
而它们的相同点:

  • 可以通过’.’来获取成员——在示例中已经给出了
  • 可以使用‘=’赋值运算符

练习题

选修部分

  1. 定义枚举类型Name,并定义几个Name类型的常量
  2. 定义结构类型Rect,使得通过长和宽(实数)就可以确定一个矩形

答案见下一篇


在下一篇(全篇选修)中,我们会来讲解关于表达式和运算符,以及动态内存分配的内容。它们都是这一篇的扩充。

猜你喜欢

转载自blog.csdn.net/qq_37422196/article/details/81488739