栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

第2章 变量和基本类型

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第2章 变量和基本类型

目录

2.1 基本内置类型

2.1.1 算数类型

2.1.2 类型转换

2.1.3 字面值常量

2.2 变量

2.2.1 变量的定义

2.2.2 变量声明和定义的关系

2.2.3 标识符

2.2.4 名字的作用域

2.3 复合类型

2.3.1 引用

2.3.2 指针

2.3.3 理解复合类型的声明

2.4 const 限定符

2.4.1 const的引用

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.1 基本内置类型

        C++中基本数据类型包括算数类型(arithmetic type)和空类型(void)。算数类型包含字符、整型数、布尔数和浮点数。空类型不对应具体的值,仅用于一些特殊的场合,如void作为函数的返回类型。

2.1.1 算数类型

算数类型分为两类:整型(包括字符型和布尔型)和浮点型。

int占用4字节,32比特,数据范围为-2147483648~2147483647[-2^31~2^31-1]。

short、int、long、long long的尺寸:sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

浮点型可表示单精度、双精度和扩展精度值。float和double分别有7和16个有效位。

signed和unsigned的区别:signed可以表示正负数或0,unsigned只能表示非负数。

2.1.2 类型转换

切要混用signed和unsigned的类型       

        如果表达式里既有signed类型又有unsigned类型,当signed类型取值为负会自动转换成unsigned类型。  

2.1.3 字面值常量

整型和浮点型字面值

        以0开头的整数代表八进制数,以0x或0X开头的代表十六进制数。

通过后缀指定字面值类型:

u or U --- unsigned        l or L --- long        ll or LL --- long long

f or F --- float        l or L --- long double

字符和字符串字面值

        字符串字面值的类型实际上是由常量字符构成的数组,字符串默认最后有一个结束符 '',所以长度会比字面值的长度多1。

转义序列

换行符        n        横向制表符        t        回车符        r        双引号        "        单引号        '

布尔字面值和指针字面值

        true 和 false 是布尔类型的字面值,nullptr 是指针字面值。

2.2 变量

2.2.1 变量的定义

        变量定义的基本形式:类型说明符 变量名1, 变量名2;

初始化

  • 初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而已一个新值来替代。
  • 列表初始化,用花括号来初始化变量。当用于内置类型的变量时,使用被列表初始化存在丢失数据的风险。
  • 变量定义时没有初始化,则会被默认初始化。定义于函数体外的变量会被初始化为0。定义于函数体内的内置类型的对象如果没有初始化,则其值是未定义的。类的对象如果没有显示地初始化,则其值由类确定。

2.2.2 变量声明和定义的关系

        为了支持分离式编译机制,C++语言将声明和定义区分开来。声明使名字为程序所知,一个文件如果想使用别处定义的名字,则必须包含对那个名字的声明。而定义负责创建与名字关联的实体,还申请存储空间,也可能会为变量赋初始值。

        声明一个变量需要在变量名前加上关键字extern,而且不要显示地初始化变量:

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

        任何包含了显示初始化的声明即成为定义。extern语句如果包含了初始值就不再是声明,而变成定义了:

extern double pi = 3.1416;    // 定义

在函数体内部,如果试图初始化一个由extern关键字标记的变量,将会引发错误 。

2.2.3 标识符

        C++标识符有字母、数字和下画线组成,必须以字母或下画线开头。

变量命名规范

  • 标识符要体现实际含义。
  • 变量名一般用小写字母,如index,不要使用Index或INDEX。
  • 用户自定义类名一般以大写字母开头。
  • 如果标识符由多个单词组成,则单词间应有明显区分,如student_loan或studentLoan,不要使用studentloan。

2.2.4 名字的作用域
#include
using namespace std;
 
int reused = 42; // reused拥有全局作用域,定义于函数体之外
int main()
{
    int unique = 0;    // unique拥有块作用域,声明周期到main函数结束为止
    // 使用全局变量reused;输出 42 0
    cout << reused << " " << unique << endl;
    int reused = 0;    // 新建局部变量reused,覆盖了全局变量reused
    // 使用局部变量reused;输出 0 0
    cout << reused << " " << unique << endl;
    // 显示地访问全局作用域中的reused;输出 42 0
    cout << ::reused << " " << unique << endl;
    return 0;
}

2.3 复合类型

        复合类型是指基于其他类型定义的类型。引用和指针。

2.3.1 引用

        引用为对象起了另外一个名字,引用类型引用另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名:

int val = 1024;
int& refVal = ival;    // refVal指向ival
int& refVal2;          // 错误:引用必须初始化

        定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。

引用即别名

引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。

一般情况下引用的类型都要和与之绑定的对象严格匹配:

int& refVal3 = 10;    // 错误:引用类型的初始值必须是一个对象
double dval = 3.14;
int& refVal4 = dval;  // 错误:类型不匹配

两种例外情况如下:

  1. 引用的类型与对象的类型存在继承关系。
  2. 引用的类型与对象的类型含有一个可接受的const类型转换规则:
double dval = 3.14;
const int& refVal = dval;

2.3.2 指针

        指针是 “指向” 另外一种类型的复合类型。指针与引用的区别:指针本身是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象;指针无需在定义时赋值。

        定义指针类型的方法将声明符写成*d的形式,其中d是变量名。指针存放某个对象的地址,使用取值符(&)获取该地址。

int ival = 42;
int* p = &ival;

一般情况下指针的类型都要和与之指向的对象严格匹配:

double dval;
double* pd = &dval;    // 正确:初始值是double型对象的地址
double* pd2 = pd;      // 正确:初始值是指向double对象的指针

int* pi = pd;          // 错误:指针pi的类型和pd的类型不匹配
pi = &dval;            // 错误:试图把double型对象的地址赋给int型指针

两种例外情况与引用一样。

指针值

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一位置。
  3. 空指针,意味着指针没有指向任何对象。
  4. 无效指针(野指针),值是未定义的指针。
int* p1 = nullptr;    // 用字面值nullptr初始化指针
int* p2 = 0;          // 直接将p2初始化为字面常量
// #include cstdlib
int* p3 = NULL;       // 等价于int* p3 = 0;

利用指针访问对象

通过解引用符 * 来利用指针访问对象,给解引用的结果赋值等价于给指针所指的对象赋值。

赋值和指针

       指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象。

        void*指针可以存放任意对象的地址,但是因为无法确定void所指向的对象是什么类型以及可以对该对象进行什么操作,所以不能直接操作void*指针所指向的对象。

2.3.3 理解复合类型的声明

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

指向指针的指针

int ival = 1024;
int* pi = &ival;    // pi指向一个int型的数
int** ppi = π    // ppi指向一个int型的指针

指向指针的引用

        引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:

int i = 42;
int* p;        // p是一个int型指针
int*& r = p;   // r是一个对指针p的引用

r = &i;       // r引用了一个指针,因此对r赋值&i就是另p指向i
*r = 0;        // 解引用r得到i,也就是p指向的对象,将i的值改为0

2.4 const 限定符
  • const 对象创建后不能进行更改,所以必须初始化。
  • 利用一个对象去初始化另外一个对象,则他们是不是const都无关紧要:
int i = 42;
const int ci = i;    // 正确:i的值拷贝给了ci
int j = ci;          // 正确:ci的值拷贝给了j
  • 默认状态下,const对象仅在文件内有效。如果要在多个文件共享const对象,必须在变量的定义之前添加extern关键字

2.4.1 const的引用

与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:

const int ci = 1024;
const int& r1 = ci;    // 正确:引用及其对应的对象都是常量

r1 = 42;               // 错误:r1是对常量的引用
int& r2 = ci;          // 错误:试图让一个非常量引用指向一个常量对象

初始化和对const的引用

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

int i = 42;
const int& ri = i;        // 允许将const int&绑定到一个普通int对象上
const int& r2 = 42;       // 正确:r1是一个常量引用
const int& r3 = r1 * 2;   // 正确:r3是一个常量引用
int& r4 = r1 * 2          // 错误:r4是一个普通的非常量引用

double dval = 3.14;
const int& refVal = dval;

2.4.2 指针和const

        指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针:

const double pi = 3.14;
double* ptr = π        // 错误:ptr是一个普通指针
const doyble* cptr = π // 正确:cptr是一个指向double常量的指针
*cptr = 42;              // 错误: 不能给*cptr赋值

指针的类型必须与其所指对象的类型一致,但是有两个例外。第一种例外情况是允许令一个指向常量的指针指向一个非常量对象:

double dval = 3.14;
cptr = &dval;        // 正确

const指针

int val1 = 0;
int* const ptr1 = &val1;    // ptr指针是一个常量,指针指向的值可以改变
int val2 = 0;
const int* ptr2 = &val2;   // 指向常量的指针,指针地址不可改变
const double pi = 3.14;
const double* const pip = π    // 指向常量对象的常量指针

2.4.3 顶层const

        顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。顶层const可以表示任意的对象是常量,如算数类型、类、指针等。

int i = 0;
int* const p1 = &i;        // 不能改变p1的值,这是一个顶层const
const int ci = 42;         // 不能改变ci的值,这是一个顶层const
const int* p2 = &ci;       // 允许改变p2的值,这是一个底层const
const int* const p3 = p2;  // 靠右的const是顶层const,靠左的是底层const
const int& r = ci;         // 用于声明引用的const都是底层const

2.4.4 constexpr和常量表达式

        常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定:

const int max_files = 20;        // max_files是常量表达式
const int limit = max_files + 1; // limit是常量表达式
int staff_size = 27;             // staff_size不是常量表达式
const int sz = get_size();       // sz不是常量表达式,尽管sz是一个常量。

constexpr变量

        C++新标准允许将变量声明为constexpr类型以便由编译器来验证变量是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:

constexpr int max_files = 20;        // 20是常量表达式
constexpr int limit = max_files + 1; // max_files + 1是常量表达式
constexpr int sz = size();           // 只有当size是一个constexpr函数时
                                     // 才是一条正确的声明语句

指针和constexpr

constexpr仅对指针有效,与指针所指的对象无关:

const int* p = nullptr;        // p是一个指向整型常量的指针
constexpr int* q = nullptr;    // p是一个指向整数的常量指针

2.5 处理类型

2.5.1 类型别名

类型别名是一个名字,它是某种类型的同义词。

关键字typedef:

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

关键字using:

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

2.5.2 auto 类型说明符
  • auto让编译器通过初始值来推算变量的类型。
  • 使用auto能在一条语句中声明多个变量,但该语句中的所有变量的初始基本类型必须一样。
  • auto一般会忽略掉顶层const。
    const int ci = i, &cr = ci;
    auto b = ci;    // b是一个int型(ci的顶层const特性被忽略了)
    auto c = cr;    // c是一个int型(cr是ci的别名,ci是一个顶层const)
    auto d = &i;    // d是一个int*型(对整数取地址)
    auto e = &ci;   // e是一个指向int型常量的指针(对常量对象取地址是一种底层const)
  • 设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。

2.5.3 decltype 类型指示符

decltype 的作用是选择并返回操作数的数据类型。

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

        如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内):

const int ci = 0, &cj = ci;
decltype(ci) x = 0;        // x的类型是const int
decltype(cj) y = x;        // y的类型是const int&,y绑定到变量x
decltype(cj) z;            // 错误:z是一个引用,必须初始化

decltype和引用

        decltype((variable))的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用才是引用。

2.6 自定义数据结构
// 头文件保护符
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include
struct Sales_item
{
    // 类的数据成员
    string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
#endif

练习

练习 2.1 :

类型int、long、long long和short的区别:C++保证short和int至少为16位,long至少为32位,long long至少为64位。

在无符号类型和有符号类型之间的区别:有符号符号可以表示正数、负数和零,而无符号符号只能表示不小于零的数。

浮点数和双精度数之间的区别:float和double分别有7和16个有效位。

练习 2.2:使用double或者float。

练习2.3:

unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl;    // 32
std::cout << u - u2 << std::endl;    // 4294967264

int i = 10, i2 = 42;
std::cout << i2 - i << std::endl;    // 32
std::cout << i - i2 << std::endl;    // -32
std::cout << i - u << std::endl;     // 0
std::cout << u - i << std::endl;     // 0

练习2.5:

(a) 字符,宽字符,字符串,字符串宽字符

(b) 十进制,无符号十进制,长十进制,无符号长十进制,八进制,十六进制。

(c) double, float, long double.

(d) int, unsigned int, double, double.

练习2.6:

int month = 9, day = 7;    // 十进制整数
int month = 09, day = 07;  // 0开头为八进制,int month = 09无效

练习2.7:

(a): string

(b): long double

(c): float

(d): long double

练习2.8:

#include
using namespace std;

int main()
{
	cout << (int)'2' << endl;	// '2'字符对应的ASIIz值 50
	cout << (int)'M' << endl;	// 'M'字符对应的ASIIz值 77
	cout << oct << 50 << endl;	// 50转换为八进制62
	cout << oct << 77 << "n";	// 77转换为八进制115
	cout << "62115n";
	cout << "62t115n";
	return 0;
}

练习2.9:

// (a)
int input_value = 0;
std::cin >> input_value;

// (b) 类型double不能在初始化列表中缩小为int。
double i = { 3.14 };

// (c) 未定义标识符"wage"
double wage;
double salary = wage = 9999.99;

// (d) 合法:但是值将被截断。
double i = 3.14;

练习2.10:

std::string global_str;    // global_str是全局变量,因此值为空字符串。
int global_int;            // global_int是全局变量,因此值为零。
int main()
{
    int local_int;         // local_int是一个未初始化的局部变量,因此它有一个未定义的值。
    std::string local_str; // local_str也是一个非未初始化的局部变量,但它有一个由类定义的值。            
                           // 所以它是空字符串。
}

练习2.11:

extern int ix = 1024;    // 定义
int iy;                  // 定义
extern int iz;           // 声明

练习2.12:

int double = 3.14;        // 非法
int _;
int catch-22;             // 非法
int 1_or_2 = 1;           // 非法
double Double = 3.14;

练习2.13:

int i = 42;
int main()
{
    int i = 100;
    int j = i;    // j == 100
}

练习2.14:

int i = 100, sum = 0;    // i,sum是全局变量
for (int i = 0; i != 10; ++i)    // i是局部变量,退出循环后i被销毁
    sum += i;
std::cout << i << " " << sum << std::endl;    // 100 45

练习2.15

int ival = 1.01;    // 合法
int &rval1 = 1.01;  // 不合法,初始值必须是一个对象。
int &rval2 = ival;  // 合法
int &rval3;         // 不合法,引用必须初始化

练习2.16:

int i = 0, &r1 = i;
double d = 0, &r2 = d;

r2 = 3.14159;    // 合法,赋值操作
r2 = r1;         // 合法,int型转换为double型
i = r2;          // 合法,double型转换为int型
r1 = d;          // 合法,double型转换为int型

练习2.17:

int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;    // 10 10

练习2.18:

int a = 0, b = 1;
int *p1 = &a, *p2 = p1;
// 更改指针的值
p1 = &b;
// 更改指针所指对象的值
*p2 = b;

练习2.19:指针和引用之间的主要区别。

定义: 指针是 “指向” 另外一种类型的复合类型。引用是对象的“另一个名称”。

指针与引用的区别:

  • 引用是已经存在的对象的另一个名称。指针本身就是一个对象。
  • 一旦初始化,引用将保持与初始对象的绑定。没有办法重新绑定引用以引用另一个对象。指针可以被赋值和复制。
  • 引用总是获取最初绑定到的对象。一个指针在其生命周期内可以指向几个不同的对象。
  • 引用必须初始化。指针在定义时不需要初始化。

练习2.20:

int i = 42;
int *p1 = &i; 
*p1 = *p1 * *p1;    // 1764(42*42)

练习2.21:

int i = 0;
double* dp = &i;    // 非法,不能用int*类型的右值初始化double*类型的变量
int *ip = i;        // 非法,不能用int型左值初始化int*型变量
int *p = &i;        // 合法

练习2.22:

if (p) // p != nullptr
if (*p) // *p != 0

练习2.23 不能确定它是否指向一个合法对象。因为需要更多的信息来确定指针是否有效。

练习2.24:

int i = 42;
void *p = &i;
long *lp = &i;

        void*类型是一种特殊的指针类型,它可以保存任何对象的地址。但是不能用int*类型的右值初始化long*类型的变量。

练习2.25:

int* ip, i, &r = i;    // ip是指向int的指针,i是int, r是指向int i的引用。
int i, *ip = 0;        // i是int,ip是空指针
int* ip, ip2;          // ip是指向int类型的指针,ip2是int。

练习2.26:

const int buf;      // 非法,buf是未初始化的const
int cnt = 0;        // 合法
const int sz = cnt; // 合法
++cnt;              // 合法
++sz;               // 非法,尝试修改const对象(sz)。

练习2.27:

int i = -1, &r = 0;         // 非法,引用必须指向一个对象
int* const p2 = &i2;        // 合法
const int i = -1, &r = 0;   // 合法,常量引用可以绑定非常量的对象、字面值,甚至是个一般表达式。
const int* const p3 = &i2;  // 合法
const int* p1 = &i2;        // 合法
const int& const r2;        // 非法,引用必须初始化
const int i2 = i, &r = i;   // 合法

练习2.28:

int i, *const cp;       // 非法,cp必须初始化
const int ic, &r = ic;  // 非法,ic必须初始化
int *p1, *const p2;     // 非法,p2必须初始化
const int *const p3;    // 非法,p3必须初始化
const int *p;           // 合法,指向const int的指针。

练习2.29:

i = ic;     // 合法
p1 = p3;    // 非法,p3是指向const int的指针
p1 = ⁣   // 非法,ic是一个const int
p3 = ⁣   // 非法,p3是一个const指针
p2 = p1;    // 非法,p2是一个const指针
ic = *p3;   // 非法,ic是一个const int.

练习2.30:

const int v2 = 0;     // v2是顶层const
int v1 = v2;    
int *p1 = &v1, &r1 = v1;
// p2是底层const;P3:最右边的const是顶层,最左边的是底层;r2是底层const
const int *p2 = &v2, *const p3 = &i, &r2 = v2;    

练习2.31:

r1 = v2; // 合法,v2是顶层const
p1 = p2; // 非法,p2是底层const,p1不是
p2 = p1; // 合法, int* 可以转换成 const int*.
p1 = p3; // 非法, p3有一个底层const,p1不是
p2 = p3; // 合法, p2和p3都有一个底层const

练习2.32:

int null = 0, *p = null;    // 非法
int null = 0, *p = nullptr;

练习2.33:

a = 42; // 赋值
b = 42; // 赋值
c = 42; // 赋值
d = 42; // 错误, d是一个int*. 正确: *d = 42;
e = 42; // 错误, e是一个const int*. 正确: e = &c;
g = 42; // 错误, g是一个const int&绑定到ci.

练习2.35:

const int i = 42;
auto j = i;                 // j是int
const auto &k = i;          // k是const int&
auto *p = &i;               // p是const int*
const auto j2 = i, &k2 = i; // j2是const int, k2是const int&

练习2.36:

int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
++c;
++d;
// c是int,d是int&,c = d = 4

练习2.37:

int a = 3, b = 4;
decltype(a) c = a;        // c是int, c = 3
decltype(a = b) d = a;    // d是int&, d = 3

练习2.38:decltype处理顶级const和引用的方式与auto的方式略有不同。decltype和auto之间的另一个重要区别是,decltype执行的推导依赖于给定表达式的形式。

int i = 0, &r = i;
// 相同:a、b都是int
auto a = i;
decltype(i) b = i;
// 不同:c是int, d是int&
auto c = r;
decltype(r) d = r;

练习2.42:

#include
#include
using namespace std;

// Sale_data类
struct Sale_data
{
    string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;

    void CalcRevenue(double price);
    void AddData(Sale_data data);
    void SetData(Sale_data data);
    void PrintData();
};

void Sale_data::CalcRevenue(double price)
{
    revenue = units_sold * price;
}

void Sale_data::AddData(Sale_data data)
{
    if (bookNo != data.bookNo)
        return;
    units_sold += data.units_sold;
    revenue += data.revenue;
}

void Sale_data::SetData(Sale_data data)
{
    bookNo = data.bookNo;
    units_sold = data.units_sold;
    revenue = data.revenue;
}

void Sale_data::PrintData()
{
    cout << bookNo << " " << units_sold << " " << revenue << " " << endl;
}

// 书店程序
int main()
{
    Sale_data total;    // 保存下一条交易记录的变量
    double totalPrice;
    // 读入第一条交易记录,并确保有数据处理
    if (cin >> total.bookNo >> total.units_sold >> totalPrice)
    {
        total.CalcRevenue(totalPrice);  // 计算total的销售额
        Sale_data trans;    // 新读取的变量
        double transPrice;
        while (cin >> trans.bookNo >> trans.units_sold >> transPrice)
        {
            trans.CalcRevenue(transPrice);  // 计算trans的销售额
            if (total.bookNo == trans.bookNo)    // 如果读取相同的书
            {
                total.AddData(trans);
            }
            else
            {
                total.PrintData();  // 输出前一本书的销售结果
                total.SetData(trans);
            }
        }
        total.PrintData();  // 打印最后一本书的结果
    }
    else
    {
        cerr << "No data?!" << endl;
        return -1;
    }
   
    return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/433635.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号