C++primer学习笔记及作业答案之第二章

本文详细介绍了C++ Primer中的第二章内容,包括变量的声明与定义,复合类型如引用和指针,const限定符的用法,以及处理不同类型的方法。特别强调了初始化与赋值的区别,引用与指针的特性,以及const对象的规则。此外,还探讨了自定义数据结构的概念。文中包含课后习题解答,帮助读者巩固理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

笔记:

string表示的是一种可变长字符序列的数据类型;

对象:一块能存储数据并具有某种类型的内存空间,变量是命名了的对象。

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

2.2 变量

变量申明规定了变量的类型和名字,而定义还申请存储空间,也可能会为变量赋一个初始值;如果想申明一个变量而非定义它,可以添加关键字 extern,而且不要显示的初始化变量。

2.3 复合类型

引用:引用必须被初始化,引用并非对象,它只是为一个已经存在的对象所起的另外一个名字,定义的时候必须初始化,而且以后一直与初始化对象绑在一起,引用只能绑在对象上,不能绑在字面值和表达式的计算结果上。

指针:存放的是另一个对象的地址,指针本身是一个对象,允许对指针进行赋值和拷贝,它可以先后指向不同的对象。指针无须在定义的时候赋初值。取地址符&,解引用符*;空指针应该使用nullptr,不建议使用NULL。指针使用的时候记得初始化,虽然不会报错,但是会发生不可预料的后果。 

void* 指针存放任意对象地址,在需要的时候可以使用类型转换成需要的类型。

2.4 const 限定符

const对象一旦创建后其值就不能再改变,所以const对象必须初始化。默认状态下,const对象仅在文件内有效。如果想在多个文件之间共享const 对象,必须在变量的定义之前添加extern关键字。

之前提到过,引用的类型必须与其所引用对象的类型一致,但是有两个例外,一个就是初始化常量引用时可以绑定非常量,甚至允许用任意表达式作为初始值。

常量引用必须初始化,常量指针必须初始化,指向常量的指针可以不初始化,指向常量的指针也没有规定其所指的对象必须是一个常量。

顶层const表示指针本身是个常量,即指针本身的值不能修改,也可以表示任意的对象是常量;底层const表示指针所指的对象是一个常量,指针的值可以修改,用于声明引用的都是底层的const。

const int *p = nullptr;     //p是一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整型的常量指针
constexpr把它所定义的对象置为了顶层const

2.5 处理类型

auto一般会忽略掉顶层const

decltype(*p)也是引用类型,表达式的内容是解引用操作,因此将得到引用类型。

2.6 自定义数据结构

类以关键字struct开始,

课后习题:

练习 2.1:类型int、long、long long 和short的区别是什么,无符号类型和带符号类型的区别是什么,float和double的区别是什么?

答:C++语言规定一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少和一个long一样大。每个的最小尺寸分别为:short,16位;int,16位;long,32位;long long,64位。 
除去布尔型和扩展的字符型外,其他整形可以划分为带符号的和无符号的两种。带符号类型可以表示正数、负数或0,无符号类型则仅能表示大于等于0的值。 
float最小尺寸为6位有效值,double最小尺寸为10位有效值;运行浮点运算符选用double 这是因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几,事实上,对于某些机器来说,双精度运算甚至比单精度还快,long double提供的精度在一般情况下是没有必要的况且它带来的 运行时消耗也不容易忽视。

练习 2.2:计算按揭贷款,对于利率,本金,和付款分别选择何种数据类型?请说明你的理由。

答:都选用double类型,因为利率涉及到小数点后面若干位,本金和付款的最小单位为分,为小数点后两位。

练习 2.3:读程序写结果

//练习2.3
#include <iostream>

int main()
{
	unsigned u = 10, u2 = 42;         //unsigned为unsigned int,占32位,4294967296
	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

	system("pause");
	return 0;
}

练习 2.4:编写程序检查你的估计是否正确,如果不正确,请仔细读本节知道弄明白问题所在。

这里要明白,将有符号数转换成无符号数时,如果有符号数为正,则直接转即可;如果有符号数为负,则因对该类型所能表示的最大数取模运算,比如a = -10,b为无符号数,则b = a的值为4294967296-10=4294967286。这里假设a为int类型,且占32位。

练习 2.5:指出下述字面值的数据类型并说明每一组内几种字面值的区别:

‘a’L'a'"a"L"a"
char a宽字符charstring a宽字符string a
1010u10 L012
int 10unsigned int 10long 10八进制12
3.143.14f3.14L0xc
double 3.14float 3.14long double 3.14十六进制C
1010u10.10e-2
int 10unsigned 10double 10double 0.01

这里值得注意的是:浮点型字面值默认的是double类型。

练习 2.6:下面两组定义是否有区别,如果有,请叙述之:

int month =9 ; day=7; 
int month =09 ; day=07;

答:有区别;第一行的定义是十进制,第二行的定义是八进制,但是第二行中有一个错误,因为八进制当中没有9。

练习 2.7:下述字面值表示何种含义?他们各自的数据类型是什么?

a. “who goes with F\145rgues?\012”  //012表示换行
b.3.14e1L    c.1024f     d.3.14L

答:a:输出为 who goes with F\145rgues?加上换行,字符串与转义序列;b:输出为 31.4,long double类型;c:非法,整型类型后面不可以加f;d:输出为 3.14,long double类型。

练习 2.8:请用转义序列编写一段程序,要求先输出2M,然后转到新一行。修改程序使其输出2,然后输出制表符,在输出M,最后转到新一行。

//练习 2.8
#include <iostream>

int main()
{
	//\115是M,\012是换行符,\t是制表符
	std::cout << "2\115\012" << std::endl; // 输出2M,然后换行
	std::cout << "2\t\115\012" << std::endl;
	system("pause");
	return 0;
}

练习 2.9:解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。 

 
a. std::cin >> int input_value; 
b. int i ={3.14}; 
c. double salsry = wage = 9999.99;
d. int i = 3.14; 

a:>> 后面不能进行变量申明,在函数体内的内置类型不被初始化,即没有定义,因此不能向里面传值;

b:列表初始化存在一个问题,即当使用列表初始化,而且初始值存在丢失信息的风险时,编译器将报错。

c:wage没有申明类型。

d:正确,但是会丢失小数点狗后面的信息。
练习 2.10:下列变量的初值分别是什么?

std::string global_str;       //为空字符串
int global_int;               //在函数外调用 为0
int main()
{
    int local_int;           //在函数类调用 不被初始化 不知道初始值
    std::string local_str;   //空字符串
}

练习 2.11 :指出下面的语句是声明还是定义:

(a) extern int ix = 1024; //定义,显示初始化抵消了extern的作用
(b) int iy;               //定义,申明并定义
(c) extern int iz;        //声明

练习 2.12 :请指出下面的名字中哪些是非法的? 

(a) int double = 3.14;   //错误 double为关键字,不能用做变量名。
(b) int _;               //正确变量以字母或下划线开始。
(c) int catch-22;        //错误,标识符只能是字母数字下划线,— 是非法的。 
(d) int 1_or_2 =1;       //错误 变量必须以字母或下划线开头 
(e) double Double =3.14; //正确 关键字区分大小写Double不为关键字

练习 2.13:下面程序中 j 的值是多少? 

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

答:100。

练习 2.14:下面程序合法吗,如果合法,它将输出什么? 

int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
    sum += i;
std::cout << i << " " << sum << std::endl;
//输出为 100  45

练习 2.15:下面的那个声明是不合法的?为什么 ?

(a) int ival = 1.01;     //合法,但是会损失精度。
(b) int &rvall = 1.01;   //不合法,因为引用不能与字面值绑定在一起
(c) int &rval2 = ival;   //合法
(d) int &rval3;          //不合法,没有初始化

练习 2.16:考查下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些是合法的?他们执行了什么样的操作? 

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

a. r2 =3.14159;   //合法,把d的值修改为3.14159 
b. r2 =r1;        //合法,将r1指向的i的值赋给r2指向的d的值
c. i=r2;          //合法,给i赋值,值为r2指向的d的值 
d. r1 =d;         //合法,将d的值赋给r1指向的i。

练习 2.17:执行下面的代码段将会输出什么结果?

int i = 0, &ri = i;
i = 5;
ri = 10;
std::cout << i << " " << ri << std::endl;
//输出 10 10,引用其实就是变量的另一个名字而已,所以给变量赋值和给引用赋值是一样的,结果都是改变了变量的值。

练习 2.18: 编写代码分别更改指针的值以及指针所指对象的值。

//练习 2.18
#include <iostream>

int main()
{
	int a = 1, b = 2, c = 3, d = 4;
	int *p1 = &a, *p2 = &b, *p3 = &c;
	//方便后面验证是否真的改变
	std::cout << p1 << "\n" << *p2 << "\n" << p3 << std::endl;
	p1 = &c;   //改变指针的值
	*p2 = d;   //改变指针所指对象的值
	std::cout << p1 << "\n" << *p2 << std::endl;
	system("pause");
	return 0;
}

练习 2.19:说明指针和引用的主要区别。

答:引用不是对象,只是变量的别名,没有实际的内存地址,指针是对象有自己本身的地址;引用必须初始化,指针不是必须初始化,但建议全部初始化;引用赋值后,不能改变指向的对象,只能改变指向对象的值;指针赋值后,既能改变指向的对象,又能改变指向对象的值。

练习 2.20:请叙述下面的这段代码的作用。

int i = 42;
int *p1 = &i;
*p1 = *p1 * *p1;
// 将42 * 42 的值赋给i。

练习 2.21:请解释下述定义。在这些定义中有非法的吗?如果有,为什么 ?

int i = 0;
(a) double *dp = &i;  //非法,指针类型与变量类型不匹配。
(b) int *ip = i;      //非法,不能将int变量直接赋给指针。
(c) int *p = &i;      //合法,p是指向变量i的一个指针。

练习 2.22:假设p是一个int型指针,请说明下面代码的含义。

if (p)    //判断p是否指向了一个合法的对象。
if (*p)   //指向的变量的值为真还是假。

 练习 2.23:给定指针p,你能否知道它是否指向了一个合法的对象吗?如果能,叙述你的思路,如果不能说明原因。 

答:使用 if(p) 来判定,因为指针存放的是对象的地址,因此可以通过 if 语句来判定指针是否指向了一个合法的对象。

练习 2.24:在下面这段代码中为什么p合法而lp非法 ?

int i = 42; 
void *p = &i;   //因为void*指针可以存放任意对象的地址,对对象的类型并没有要求
long *lp = &i;  //指针的类型与所指向对象的类型不匹配,所以是非法的。

练习 2.25:说明下面变量的类型和值。

(a) int* ip,i, &r = i; //ip是int型指针,i是int型变量,r是一个int型引用。
(b) int i, *ip = 0;    //i是一个int型变量,ip是一个int型空指针,。
(c) int* ip, ip2;      //ip是一个int型指针,ip2是一个int型指针。

 练习 2.27:下面的那些初始化是合法的,如果有不合法的句子,请说明为什么?

(a) const int buf;        //非法,const常量必须初始化
(b) int cnt = 0;         //合法
(c) const int sz = cnt;  //合法
(d) ++cnt; ++sz;         //++cnt合法,++sz非法,不能对const常量进行修改

练习 2.27:下面的哪些初始化是合法的?请说明原因。

(a) int i = -1, &r = 0;        //非法,引用的初始化不能是字面值。
(b) int *const p2 = &i2;       //合法
(c) const int i = -1, &r = 0;  //合法,常量引用的初始化可以是字面值和表达式。
(d) cost int *const p3 = &i2;  //合法
(e) const int *p1 =& i2;       //合法
(f) const int &const r2;       //非法,引用不能是const
(g) const int i2 = i, &r = i;  //合法

练习 2.28:说明下面的这些定义是什么意思,挑出其中不合法的。

(a) int i, *const cp;      //非法,cp是常量指针,必须初始化。
(b) int *p1, *const p2;    //非法,p2是常量指针,必须初始化。
(c) const int ic, &r = ic; //非法,IC没有初始化
(d) const int *const p3;   //非法,p3是一个指向常量的常量指针,也需要初始化。
(e) const int *p;          //合法。指向常量的指针可以不初始化。

练习 2.29:假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。

(a) i = ic;    //合法
(b) p1 = p3;   //非法,p3是一个指向常量的常量指针,p1是一个int型指针
(c) p1 = &ic;  //ic是一个int型常量,p1只是一个int型指针,必须要是指向常量的指针
(d) p3 = &ic;  //非法,p3是一个常量指针,不能再次被赋值
(e) p2 = p1;   //非法,p2是一个常量指针,不能被赋值
(f) ic = *p3;  //非法,ic是常量

练习 2.30:对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?

const int v2 = 0;   int v1 = v2;                //v2是顶层const
int *p1 = &vi, &ri = v1;                        //
const int *p2 = &v2, *const p3 = &i, &r2 = v2;  //p2是底层,p3既是顶层,又是底层,r2是底层

练习 2.31:假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。

r1 = v2;  //合法。
p1 = p2;  //非法,p2是一个指向常量的指针,p1只是一个指针
p2 = p1;  //合法
p1 = p3;  //非法,p3是一个指向常量的常量指针,p1只是一个指针
p2 = p3;  //合法
//上面非法的原因的另一种解释是,底层const不一样。

练习 2.32:下面代码是否合法?如果非法,请假设将其修改正确 。

int null = 0, *p = null; //非法,因为int型的变量不能直接赋值给指针,即使int型变量的值等于0。
//改正
int null = 0, *p = &null;

练习 2.33:利用本节定义的变量,判断下列语句的运行结果 。

a = 42;  //合法,a是int型
b = 42;  //合法,b是int型
c = 42;  //合法,c也是int型
d = 42;  //非法,d是int *
e = 42;  //非法,e是const int *
g = 42;  //非法,g是const int &

练习 2.34:基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节示例直到明白错在何处为止。

//练习 2.34
#include <iostream>

int main()
{
	int i = 0, &r = i;
	auto a = r;

	const int ci = i, &cr = ci;
	auto b = ci;
	auto c = cr;
	auto d = &i;
	auto e = &ci;

	const auto f = ci;
	auto &g = ci;
	a = 42; 
	b = 42; 
	c = 42;
	*d = 42;
	e = &c;
	//g的值不能改变
	std::cout << e << std::endl;
	system("pause");
	return 0;
}

练习 2.35:判断下列定义推断出的类型是什么,然后编写程序验证。

const int i = 42;             //i是const int
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;     //a是 int,b是 int
decltype(a) c = a;    //c是 int
decltype((b)) d = a;  //d是 int &,是引用,和a绑在一起了,所以改变d也就改变了a
++c;                  //c = 4
++d;                  //d = 4
// a = 4, b = 4

练习 2.37:赋值是会产生引用的一类型表达式,引用的类型就是左值的类型。也就是说,如果i是int,则表达式i = x的类型是int&。根据这一特点,请指出下面的代码每一个变量的类型和值 。

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

练习 2.38:说明由decltype指定类型和由auto指定类型有何区别,请举出一个列子,decltype指定的类型与auto指定的类型一样;在举一个列子,decltype指定的类型与auto指定的类型不同。

主要的区别有两点:

1:如果使用引用类型,auto会识别为其所指对象的类型,decltype则会识别为引用的类型。

2:decltype(())双括号的差别。

int i = 0, &r = i;
// same
auto a = i;         //int a
decltype(i) b = i;  //int b
// different
auto c = r;         //int c
decltype(r) d = i;  //int & d

练习 2.39:编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关信息,以后可能会有用。 

//练习 2.39
struct Foo
{
	/* 此处为空 */  
}   //注意:没有分号
int main()
{
	return 0;
}

练习 2.40:根据自己的理解写出Sales_data类,最好与书中的例子有所区别。

//练习 2.40
#include <iostream>

struct Sales_data
{
	int a = 0;
	double b = 0.0;
	std::string sale_sum;
};

练习 2.41:使用你自己的Sales_data 类重写1.51节(第20页),1.52节(第21页),1.6节(第22页)的练习。眼前先把Sales_data 和main函数放在同一个文件。

//练习 2.41
#include <iostream>
#include <string>

struct Sales_data
{
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;
};

int main()
{
	Sales_data data1, data2;
	int Sum_units_sold = 0;
	double Sum_revenue = 0;
	std::cin >> data1.bookNo >> data1.units_sold >> data1.revenue;
	std::cin >> data2.bookNo >> data2.units_sold >> data2.revenue;
	if (data1.bookNo == data2.bookNo)
	{
		Sum_units_sold = data1.units_sold + data2.units_sold;
		Sum_revenue = data1.units_sold * data1.revenue + data2.units_sold * data2.revenue;
		std::cout << data1.bookNo << " " << Sum_units_sold << " " << Sum_revenue << std::endl;
	}
	else
	{
		std::cout << "The book's 优快云 is different!" << std::endl;
	}
	system("pause");
	return 0;
}

练习 2.42,2.43只重写练习1.21,其他的自行尝试。

练习 2.43:根据你自己的理解重写一个Sales_data.h头文件,并以此为基础重做2.6.2节(第67页)的练习。

//练习 2.42 头文件
#ifndef SALES_DATA_H     //头文件保护符
#define SALES_DATA_H
#include <string>
struct Sales_data
{
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;
};
#endif
//练习 2.42

#include <iostream>
#include <string>
#include "Sales_data.h"

int main()
{
	Sales_data data1, data2;
	int Sum_units_sold = 0;
	double Sum_revenue = 0;
	std::cin >> data1.bookNo >> data1.units_sold >> data1.revenue;
	std::cin >> data2.bookNo >> data2.units_sold >> data2.revenue;
	if (data1.bookNo == data2.bookNo)
	{
		Sum_units_sold = data1.units_sold + data2.units_sold;
		Sum_revenue = data1.units_sold * data1.revenue + data2.units_sold * data2.revenue;
		std::cout << data1.bookNo << " " << Sum_units_sold << " " << Sum_revenue << std::endl;
	}
	else
	{
		std::cout << "The book's 优快云 is different!" << std::endl;
	}
	system("pause");
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值