一枚正在学习C++ Primer小菜鸟的阅读笔记

2 变量与基本类型

charshortintlongfloatdouble指针
32位系统字节数1(8)2(16)4(32)4(32)4(32)8(64)4(32)
64位系统字节数1(8)2(16)4(32)8(32)4(32)8(64)8(64)
字数一个字一个字两个字
有效位0716
最大值42 9496 7296****

注:内存Buffer运算一般以字节为单位;上述字数转换使用一个字=四个字节

2.0 float与Double 精度与有效位

符号位s指数位E
是有符号的
比较特殊,需要移位
尾数位M
只是小数点后面的位数哈
float18
(-127~128)
23
double111
(-1023~1024)
52
  • 表示方法:(-1)^ S*1.M *2^E
    这里的E是十进制的哈。

  • 精度
    float: -2^ 128~2^128=-3.40E+38 ~ +3.40E+38
    double:-2^ 1024~2^1025=-1.79E+308 ~ +1.79E+308

  • 有效位
    2^23=838,8608(7位,但是最后一位由于四舍五入不一定准确,所以精确度6位肯定是准确的)
    2^52=4503,5996,2737,0496(16位,但是最后一位由于四舍五入不一定准确,所以精确度15位肯定是准确的)

参考链接 https://blog.csdn.net/weixin_43791071/article/details/122361036

2.0 整数作为Bool值判断 VS 整数与Bool值比较

if(i) VS if(i == true)
if(!i) VS if(i == false)

  • 注:整数与Bool值比较,等价于整数与整数比较,即Bool量自动转换为整型(false为0;true为1)
#include <iostream>
using namespace std;

int main()
{
	for(int i = -5; i <= 5; i++)
	{
		if(i == true)
		{
			cout << i << " == true" << endl;
 		}
		else if(i == false)
		{
			cout << i << " == false" << endl;
		}
		else
		{
			cout << i << " wrong!" << endl;
		}
	}
	cout << endl;
	for(int i = -5; i <= 5; i++)
	{
		if(i)
		{
			cout << i << " true" << endl;
 		}
		else if(!i)
		{
			cout << i << " false" << endl;
		}
		else
		{
			cout << i << " wrong!" << endl;
		}
	}
	
}

输出如下

-5 wrong!
-4 wrong!
-3 wrong!
-2 wrong!
-1 wrong!
0 == false
1 == true
2 wrong!
3 wrong!
4 wrong!
5 wrong!

-5 true
-4 true
-3 true
-2 true
-1 true
0 false
1 true
2 true
3 true
4 true
5 true

2.0类型转换:

2.0.1 char 2 int、char 2 hex

#include <iostream>
using namespace std;

int main()
{
	// 1.1 char to int(real)
	cout << "char to int(real): [int(x)] " <<endl;
	char a1 = '\n';
	char a2 = '\r';
	int ia1 = (int)a1; 
	int ia2 = (int)a2; 
	/* note that the int cast is not necessary -- int ia = a would suffice */
	cout << "\\n(换行): " << ia1 << endl;
	cout << "\\r(回车): " << ia2 << endl << endl;
	
	// 1.2 char to int(superficial)
	cout << "char to int(superficial): [x - '0']"<<endl;
	char b = '0';
	int ib = b - '0';
	cout << "‘0’: " << ib << endl << endl;
	/* check here if ia is bounded by 0 and 9 */
	
	// 2. char to Hexadecimal(0x,real)
	cout << "char to Hexadecimal(0x,real):[sprintf(str, \"%x\", 'a')]" <<endl;
	char str[255] = {0}; 
	sprintf(str, "%x", 'a'); //将100转为16进制表示的字符串。
	cout << "str:" << str << endl;
	cout << "the size of str:" << sizeof(str) / sizeof(char) << endl;
	string ss(str);
	cout << "ss:" << ss << endl;
	cout << "ss.size():" << ss.size() << endl;
   return 0;
}

输出如下:

char to int(real): [int(x)] 
\n(换行): 10
\r(回车): 13

char to int(superficial): [x - '0']
‘0’: 0

char to Hexadecimal(0x,real):[sprintf(str, "%x", 'a')]
str:61
the size of str:255
ss:61
ss.size():2

2.0.2 signed char 2 unsigned char

Binary0000000000000001011111111000000010000001111111001111111011111111
signed01127-128-127-3-2-1
unsigned01127128129253254255
十进制0123127-128-127-3-2-1
原码00000000000000010000000200000003011111111000000011111111100000111000001010000001
反码
(除符号位取反)
000000000000000100000002000000030111111111111111100000001111111001111110111111110
补码
(反码+1)
00000000000000010000000200000003011111111000000010000001111111011111111011111111
unsigned0123127128129253254255
  1. 关于超出范围的实际结果
  • 带符号类型:结果未定义
    why? 感觉32页给出了答案,如果我是现代计算器,我也不知道怎么算!
C++标准中并没有规定带符号数类型如何表示,
但规定了在表示范围内正值与负值的量应该均衡。
因此,8比特的signed char理论上应该表示-127~127区间内的值。
但是!!!   大多数现代计算机将实际的表示范围规定未-128~127
  • 无符号类型:初始值对无符号类型总数取模后余数。
取模运算在计算时  商值-->向负无穷方向舍弃小数位
取余运算在计算时  商值-->向0方向舍弃小数位

例如:
-258➗256=1.0078125
-258对256取模:-258%256=-2(即商-1,余-2)
-258对256取余:-258/256=254(即商-2,余-254)

-1➗256=0.00390625
-1对256取模:-1%256=255(即商-1,余255)
-1对256取余:-1/256=-1(即商0,余-1)

  1. List item

2.0.3 others:

  • 非布尔类型–>布尔类型:初始值为0,结果false;否则结果为true。
  • 布尔值-(Bool)->非布尔时:初始值为false,结果为0;初始值为true,结果为1
  • 浮点型–>整数类型:近保留浮点数中小数点之前的部分
  • 表达式中既有带符号类型又有无符号类型:带符号数自动转换为无符号数
    切勿混用带符号类型和无符号类型
   unsigned u = 11;
   while(u > 0)
   {
	   cout << u << endl;
   }

2.1 算数类型与空类型

mermaid
graph LR
A[算数类型]  --> B[integaral type-整型/字符/布尔]
A --> C[浮点型]
E[空类型] 

注:空类型(不对应任何值)------>函数不返回任何值(常见示例)

2.1.3 字面值常量

  1. 整型和浮点型字面值
    20 /* 十进制 /
    024 /
    八进制 /
    0x14 /
    十六进制 */
    浮点型:一个小数、或以科学计数法表示的指数(指数部分用E或e标识)
    3.14159
    0e0
    .001
  2. 字符字面值、字符串字面值
  3. 转义字符:
    无法打印的字符:\n、\t、\V
    特殊含义的字符:单引号、双引号、问号、反斜线

    "
    ?
    \
  4. 指定字面值类型
  • (前缀)字符、字符串字面值
    L’a’ //宽字符型字面值(由两个字符表示?), wchar_t
    u8"hi" //utf-8字符串字面值(utf-8用8位编码一个Unicode字符)

  • 整型字面值(后缀)
    42ULL //无符号整型字面值,类型是usigned long long

  • 浮点型字面值(后缀)

  • 1E-3F

  • 3.14159L

2.2 变量

2.2.1 变量定义(分号结束)——类型说明符+变量名列表;

  1. 何为对象?
  • 具有某种类型的内存空间
  • 其他分类:类与对象、对象与未命名对象、对象与只读数据
  1. 初始值:对象在 创建 时获得了一个特定的值。
  • 注: 初始化不是赋值,赋值是把对象当前值擦除、以一个新值替代。
  1. 列表初始化 int a{ld};
  • 注,列表初始化好处:初始值存在丢失风险时,编译器将报错。
    实例如下:
long double ld = 3.1415926536;
int a{ld}, b = {ld}       // 错误,转换未执行,因为错在丢失风险
int c(ld), d = ld;         // 正确,转换执行,且确实丢失了部分值
  1. 默认值初始化:定义变量时没有指定初始值
  • 在任何函数体之外:变量被初始化为0;
  • 函数体内部的内置类型:不被初始化。
    一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝、或以其他形式访问此类值,将引发错误。
    类的对象如果没有显式初始化,则其值由类确定

2.2.2 变量生命与定义的关系

  • 变量只能定义一次,但可以被多次声明。
  • extern:声明变量而非定义;不要用extern的同时显示初始化变量,否则变成了定义;
    • 在函数内部:若试图初始化一个由**extern关键字标记的变量,将引发错误。

2.2.3 标识符:字母、数字、下划线组成;必须由字母、下划线快开始

2.2.4 名字作用域

2.3 复合类型

  • 基本类型
  • 声明符列表(变量名、与基本类型有关的某种类型)

2.3.1 引用:别名(对象的“帽子”,并非对象)

  • 定义时必须初始化(要不然我知道这是谁的帽子?)

2.3.2 指针

  • 定义时必须初始化(要不然我知道这是谁的帽子?)

2.4 const限定符

  • const变量:只能在const类型对象上执行不改变其内容操作。
  • const变量仅在(自身)文件内有效:编译时进行常量替换,可避免多个文件进行重复定义。
    如何在多个文件中进行共享?
    答:添加extern关键字
//file_1.c定义并初始化了一个变量,该变量能被其他文件访问
extern const int bufSize = fuc();


//file_1.h或者file_2.h头文件
extern const int bufSize;   // extern作用:bufSize并未本文件所有,它的定义在别处出现   

2.4.1 const的引用(对常量的引用–>常量引用)

  • 引用类型必须与引用对象一致例外:常量引用绑定非常量、字面值、表达式(只要能转化为引用即可)
int i = 42;
const int &r1 = i;
const int &r2 = 43;
const int &r2 = r1 * 2;
  • 当常量引用类型与所引用对象类型不同时,或所引用对象为表达式时,
    内部自身创建临时量,故引用与原始引用变量没有关系。(example1和example2)
  • 非常量可以使用常量引用,仅仅常量本身不可变,但非常量改变时,常量引用亦会发生改变。(example3)
#include <iostream>
using namespace std;

int main()
{
    // example1
    double d = 3.1415926;
    const int &i1 = d;   // 等效为 int temp = d; const int &i1 = temp;
	cout << "i1:" << i1 << endl;
	d = 20;
	cout << "i1:" << i1 << endl << endl;
	
	// example2
	int i = 3;
	const int &i2 = i * 2;   // 等效为 int temp =  i * 2; const int &i2 = temp;
	cout << "i2:" << i2 << endl;
	i = 10;
	cout << "i2:" << i2 << endl << endl;
	
	// example3
	i = 3;
	const int &i3 = i;
	cout << "i3:" << i3 << endl;
	i = 10;
	cout << "i3:" << i3 << endl;
    return 0;
}

输出如下

i1:3
i1:3

i2:6
i2:6

i3:3
i3:10

2.4.2.1 指向常量的指针(pointer to const,底层const,指针常量;复合类型中才有底层const)

注:一般的,指针的类型必须与所指对象类型一致。例外情况:允许令一个指向常量的指针指向一个非常量对象;

double dval = 3.14;
const double *cptr;   // 指针常量
cptr = &dval;

常量引用、指针常量,是引用或者指针‘自以为是’,觉得自己之指向了常量,所以不自觉不去改变所值的对象的值。

2.4.2.2 const pointer(pointer to const,顶层const,常量指针)

const double pi = 3.1415926;
const double *const pip = &pi;  // 指向常量的常量指针

注:此处指向常量和常量指针定义采用C++ Primmer,可能与流程说法不同。

2.4.3 顶层const

  1. 指针、引用与const
Created with Raphaël 2.3.0 指针(顶层const) 所指对象(底层const)
// examlpe
const int ci =42;  // 不能改变ci的值,这是一个顶层const
consdt int *p2 = &pi;  // 允许改变p2的值,这是一个底层const
const int *const p3 = p2; // 左边是底层const(指针所指对象是一个常量)
                                       // 右边是顶层const (指针对象是一个常量)
const int &r =ci;              // 用于声明的的const都是底层const

注:

  • 如果按照上面那个图理解,引用这里有点特殊,需要着重记一下;
  • 底层应用目前仅见过复合类型的指针和引用中。
  1. 执行拷贝时:顶层const不受影响;底层const不能忽略
  • 顶层const可以花心,想宠幸谁都行;
  • 但是底层const找对象要求高,不能匹配非底层const指针和引用。
    底层const说对顶层const说:我不在乎你是否专一,但是你的品质必须要高,不能把我带环了(不能改变底层const的值)。
// example
int i =3;
const int *const p3 = &i;
int *p4 = p3;//错误,p3说:不行啊,我的女朋友要求比较高,你配不上
int *p5 = p3;//错误,p3说:不行啊,我的女朋友要求比较高,你配不上
const int p6 = p3; //正确,p3说:还是老六质量高啊(老六是个底层const)

const int ci = 42;
int &r = ci; //错误,ci说:不行啊,你这顶帽子Low了
int &r2 = ci; //正确,ci说:我就是需要质量高的帽子

2.4.4 constexpr和常量表达式

  • 常量表达式: 修饰在编译过程中就能得到计算结果的表达式;
  • constexpr:由编译器进行校验是否为常量表达式;
  • 函数体内部定义的变量没有存放在固定地址中,不能使用constexpr修饰;错误示例如下:
#include <iostream>
using namespace std;

int main()
{
    constexpr int i = 42;
	constexpr const int *p = &i;
    return 0;
}

报错如下:

main.cpp: In function ‘int main()’:
main.cpp:7:27: error: ‘& i’ is not a constant expression
    7 |  constexpr const int *p = &i;
      |                           ^~

正确应该修改为

#include <iostream>
using namespace std;

constexpr int i = 42;
constexpr const int *p = &i;
int main()
{
    //constexpr int i = 42;
	//constexpr const int *p = &i;
	cout << "*p: " << *p << endl;
    return 0;
}

课后遗留疑问:那么constexpr一般只在函数外部使用是吗?

指针与constexpr

  • constexpr指针的初始值:必须是nullptr或者0,或者是某个固定地址的对象
  • 限定符仅仅对指针有效,与指针所指对象无关。(即:会把指针转换为顶层const?)
int j = 0;
constexpr int *p1 = &j;  // p1是常量指针,指向整数j
                        // 等效为int *const p1 = &j;

2.5 处理类型

2.5.1 类型别名(typedef)

  1. 类型别名(typedef)
    类型别名用于复合类型或者常量,用到哦声明语句中,需特别注意
typedef double wages;  // wages 是double同义词
typedef wages bae, *p  // wages 是double同义词,p是double*同义词
typedef char *pstring;  
const pstring cstr = 0;  // cstr是指向char的常量指针
const pstring *ps;        // ps是一个指针,他的对象是指向char的常量指针
                                   // 类似于const *const *ps;

注:
1)const 是对给定类型的修饰
2)使用类型别名声明后,pstring的基本类型是指针;
区别:使用char *重写后,基本数据类型是const char, *变成了声明符的一部分。

  • 别名声明:
using SI = double;
  1. 3.6.5节 类型别名 对 多维数组的指针
  2. 类型别名与函数指针
typedef char (*p)(int);      // 声明函数指针
p pFun;                          // 创建函数指针对象;
char glFun(int a){ return;}   
void main()   
{   
    pFun = glFun;   // 初始化函数指针对象;
    (*pFun)(2);   
}

2.5.2 auto类型说明符

  1. 引用作为初始值时:真正参与初始化的是引用对象的值,引用对象的类型作为auto的类型
int i = 0, &r = i;
auto a = r; // a是一个整数(r是i的别名,而i是一个整数)
  1. auto一般会忽略顶层const,底层const保留下来;
const int ci = i, &cr = ci;
auto b = ci; //  b是一个整数(ci的顶层const特性被忽略)
auto c = cr; // c是一个整数(cr是ci的别名,ci本身是一个顶层const)

auto d = &i; // d是一个指向整型针(整数的地址就是**指向整数的指针**)
auto e = &ci; // e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
  1. 若希望推断出的auto类型是一个顶层const,需明确指出:
const auto f = ci; // ci的推演类型是int,f是const int
  1. 可将引用的类型设为auto
auto &g = ci; // g是一个整型常量的引用,绑定到ci
auto &h = 42; // 错误,不能将非常量引用绑定到字面值
const auto &j = 42; // 可以为常量引用绑定字面值
  1. 一条语句中定义多个变量,初始值必须是同一种类型(&和*只从属于声明符,而非基本数据类型的一部分)
auto k = ci, &l = i; // k是整数(顶层const被忽略),l是整形引用
auto &m = ci, *p = &ci; // m是整型常量的引用,p是指向整型常量的指针
auto &n = i, *p2 = &ci; // 错误,i类型是int,而&ci类型是const int???

2.5.3 decltype类型指示符

  • 目的:只想用表达式的类型,不想用表达式的值;
  1. decltype和引用
int i = 42, *p = &i, &r = i, j = 43;
decltype(r) b = &j;  // b 是一种引用类型
decltype(r + 0) c;  // c是一个(未初始化_的int,因为加法的结果是int
decltyp(*p) d; // 错误:c的类型是int &(引用),必须初始化

*p中*为解引用,解引用指针可以得到指针所指对象,还能给该对象赋值,故decltyp(*p)结果类型是int&,而非int。

  1. decltype((variable)),双括号永远是引用。

2.6定义自己的数据结构

  • 数据结构:将相关数据元素组织在一起;然后使用它们的策略和方法。

2.6.1定义Scales_data类型

  • struct和class有什么不同?(默认访问权限不同)
  1. 类的定义不要忘记加分号
    因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少。(可以类比普通变量的定义)
struct Scales_data
{
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
};

// First choice to definite an object according with the class(not goog)
struct Scales_data{\* ... *\} accum,trans, *salesptr;
// Second choice to definite an object according with the class
struct Scales_data{\* ... *\};
Scale_data accum,trans, *salesptr;

注:最好将类定义和对象定义分开,所以第二种方式较好。

  1. 类内初始化:创建对象时,用于初始化数据成员。没有执行初始化的成员执行默认初始化。

2.6.2使用Scale_data类

2.6.3编写自己的头文件

(1)#define 预处理命令可以避免重复包含(重复包含头文件,会触发重定义问题)。
(2)头文件一旦改变,相应的源文件必须重新编译以获得更新后的声明;
(3)预处理变量无视C++语言关于作用域的规则。

3 字符串、向量和数组

3.2 标准库类型string

3.2.1 定义和初始化string对象

3.2.2 string对象上的操作

3.2 编写一段程序从标准输入中一次读入一整行,然后修改程序使其一次性读入一个词
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main()
{
	cout << "start cout in word!!!" << endl;
    string temp_a = "111 222 333 \n 444, 555 777\n 888 999 1111";
	string line = "";
	istringstream aa(temp_a);
	istringstream bb(temp_a);
	while(getline(aa, line))
	{
		cout << line << endl;
	}
	cout << endl<< endl;
	cout << "start cout in word!!!" << endl << endl;
	while(bb>>line)
	{
		cout << line << endl;  // doesn‘t cont backword
	}
   return 0;
}

输出如下

start cout in word!!!
111 222 333 
444, 555 777
888 999 1111


start cout in word!!!

111
222
333
444,
555
777
888
999
111

其他人的回答

#include <iostream>
using namespace std;
int main()
{
    /*string word;
	while(cin>>word)
	{
		cout << word << endl;
	}*/
	
	string line;
	while(getline(cin, line))
	{
		cout << line << endl;
	}
   return 0;
}

3.2.3 处理string中的字符

3.2.* string其他用法(find,substr,string::npos)

示例如下

#include <iostream>
#include <string>

using namespace std;

int main()
{
	string sa = "123456789";
	string sb = "1239";
	cout << "sa: " << sa << endl;
	cout << "sb: " << sb << endl;
	if(sa < sb)
	{
		cout << "sa < sb" << endl;
	}
	else if(sa > sb)
	{
		cout << "sa > sb" << endl;
	}
	else
	{
		cout << "sa = sb" << endl;
	}
	size_t position_start = sa.find("34");
	if(position_start != string::npos)
	{
		cout << position_start << endl;
		string ssa = sa.substr(position_start,sizeof("78")/sizeof(char));
		cout << "ssa: " << ssa << endl;
	}
	return 0;
}

3.3标准库类型vector

3.3.1 定义和初始化vector对象

3.3.2 向vector对象中添加元素

3.3.3 其他vector操作

注意:vector是一个类模板,若初始化为空vector,不能使用vector[i]进行赋值, vector只能对确定已知的元素执行下标操作

疑问: 1.如何删除vector中的元素
ivector.erase(ivector.begin()+1);
ivector.erase(ivector.begin()+1, ivector.begin()+3);

疑问: 2.如何插入vector中的元素
ivector.insert(ivector.begin()+2,11111);
注:迭代器在被插入或者删除元素后,迭代器都会失效
so: 使用了迭代器的循环体,都不要像迭代器所属容器添加元素(来源:C++ Primmer第5版P99)

#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main()
{
   vector<int> ivector;
	vector<int>::iterator iter;
	ivector.push_back(0);
	ivector.push_back(10);
	ivector.push_back(20);
	ivector.push_back(30);
	ivector.push_back(40);
	cout << "the oral vector is :" << endl; 
	for(iter=ivector.begin();iter<ivector.end();iter++)
	{
		cout << " " << *iter;
	}
	ivector.insert(ivector.begin()+2,11111);
	cout << endl << "after insert 1111 in the fourth position, the vector is :" << endl; 
	for(iter=ivector.begin();iter<ivector.end();iter++)
	{
		cout << " " << *iter;
	}
	ivector.erase(ivector.begin()+1);
	cout << endl << "after delete the first order position, the vector is :" << endl; 
	for(iter=ivector.begin();iter<ivector.end();iter++)
	{
		cout << " " << *iter;
	}

	ivector.erase(ivector.begin()+1, ivector.begin()+3);
	cout << endl << "after delete the first order and the second position, the vector is :" << endl; 
	for(iter=ivector.begin();iter<ivector.end();iter++)
	{
		cout << " " << *iter;
	}
   return 0;
}

3.4 迭代器介绍

3.4.1 使用迭代器

3.4.2 迭代器运算

疑问: 3.迭代器可以知道自己在容器中的位置吗?
可以通过distance(vector.begin(),iteror)实现, 示例如下:

#include <iostream>
#include <vector>
using namespace std;
 
int main () {
    vector<int> mylist;
	for (int i=0; i<10; i++) 
	{
		mylist.push_back (i*10);
	}

	vector<int>::iterator first = mylist.begin();
	vector<int>::iterator last = mylist.end();
	vector<int>::iterator it = first;
	for(;it != last;++it)
	{
		cout<<"第"<<distance(first,it)<<"个元素的值为:"<<*it<<endl;
	}
	return 0;
}
3.24 依次输出首位两元素的和
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> ivector;
	cout << "ivector's size is: " << ivector.size() << endl;
	int itemp;
	while(cin >> itemp)
	{
		ivector.push_back(itemp);
	}
	vector<int>::iterator iterbegin = ivector.begin(), iterend = ivector.end();
	if(iterbegin == iterend)
	{
		cout << "this is a empty vector" << endl;
		return -1;
	}
	iterend -= 1;
	int order = 1;
	while(iterbegin <= iterend)
	{
		if(iterbegin == iterend)
		{
			cout << "the last result of addition: " << *iterbegin << endl;
 		}
		else
		{
			cout << "the " << order << " result of addition: " << (*iterbegin + *iterend) << endl;
		}
		order += 1;
		iterbegin += 1;
		iterend -= 1;
	}
	
   cout << "Hello World";
   return 0;
}
3.17 从cin读入一组此,并把把他们存入vector对象,然后设法把所有词都改写成大写形式。输出改变后的结果,每个词占一行。(实例:auto)

-使用for(新特性?)循环处理多维数组,除了最内层循环外,其他所有循环控制变量都应该使用引用类型
否则如下如果初始化row时,row是指向数组首元素的指针,类型是int *,内层循环不合法(我试了下,内部解引用好像也会报错)。

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<string> svector;
	string temp;
	while(cin >> temp)
	{
		svector.push_back(temp);
	}
	
	for(auto &s:svector)
	{
		for(auto &c:s)
		{
			c = toupper(c);
		}
	}
	for(auto s: svector)
	{
		cout << s << endl;
	}
   return 0;
}
3.19练习题答案
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> a(10, 42);
	vector<int> b{42, 42,42, 42, 42, 42, 42, 42, 42, 42};
	cout << "vector a' elements: ";
	for(auto &i: a)
	{
		cout << i << ", ";
	}
	cout << endl;
	cout << "vector b' elements: ";
	for(auto &i: b)
	{
		cout << i << ", ";
	}
	cout << endl;
	vector<int> c;
	for(int i =0 ; i <10; i++)
	{
		c.push_back(42);
	}
	cout << "vector c' elements: ";
	for(auto &i: c)
	{
		cout << i << ", ";
	}
	cout << endl;
   return 0;
}
3.20练习题答案

读入一组整数并把它们存入一个vector,每对相邻整数的和输出出来;改写你的程序,这次要求先输出第一个和最后一个的和,接着输出第2个和倒数第2个数元素的和,以此类推。
PS:感觉两问差不多,我只做了第二问。

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> a;
	int temp;
	while(cin >> temp)
	{
		a.push_back(temp);
	}
	
	vector<int>::size_type m_size = a.size();

	if(!(m_size%2))
	{
		for(int i = 0; i <= (m_size-2); i+=2)
		{
			cout << (i/2+1) << ":" << a[i] + a[i+1] << endl;
		}
	}
	else
	{
		for(int i = 0; i <= (m_size-3); i+=2)
		{
			cout << (i/2+1) << ":" << a[i] + a[i+1] << endl;
		}
		cout << (m_size/2 +1) << ": " << a[m_size-1] << endl;
	}
   return 0;
}
3.24请使用迭代器重做3.3.3节(第94页)的最后一个练习。
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> ivector;
    int itemp;
	while(cin >> itemp)
	{
		ivector.push_back(itemp);
	}
    auto first_iter = ivector.cbegin(), last_iter = ivector.cend();
    if(first_iter == last_iter)
	{
		cout << " input is empty, please check again." << endl;
		return 0;
	}
	--last_iter;
    int counter_couple = 0;
    while(first_iter < last_iter)
	{
		cout << "the " << ++counter_couple << " couple: " << (*first_iter + *last_iter) << endl;
		++first_iter;
		--last_iter;
	}
    if(first_iter == last_iter)
	{
		cout << "the " << ++counter_couple << " couple: " << *first_iter << endl;
	}
    return 0;
}
3.25 3.3.3节(第93页)划分分数段的程序使用下标运算符实现的,请利用迭代器改写该程序并实现完全相同的功能
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> ivector(11);
    int itemp;
	auto first_iter = ivector.begin();
	cout << "the socres is: ";
	while(cin >> itemp)
	{
		cout << itemp << " ";
		(*(first_iter + itemp / 10))++;
	}
	cout << endl;
	
    for(int i = 0;first_iter != ivector.end() - 1; first_iter++)
	{
		cout << "The number between the " << i * 10 << "~" << i * 10 +9 << " in socres has: " << *first_iter << endl;
		i++;
	}
	cout << "The number get 100 in socres has: " << *(ivector.end() - 1) << endl;
	
    return 0;
}

3.5数组

const关键字修饰数组:要求定义时就要赋予初值,且之后数组内元素不可改变。

3.5.1 定义和初始化内置数组

3.27 下列定义非法吗?

以下定义非法:无符号数buf_size不是常量;

unsigned buf_size = 1024;
int ia[buf_size];              

数组维度定义有两个要求:
1.大于0
2.常量表达式

3.28 下列数组中元素的值是什么?

全局变量和局部变量的区别、内置类型与复合类型的区别。
** String **:接受无参数的初始化方式。
所以无论数组在函数内还是函数外,都被默认初始化为空字符串。
** int(内置类型) **:
函数外:所有元素初始化为0;
函数内不被初始化,程序试图拷贝或输出未初始化的变量,将遇到未定义的奇异值。

#include <iostream>
#include <string>
using namespace std;

string sa[10];
int ia[10];
int main()
{
    string sa2[10];
	int ia2[10];
	cout << "sa: ";
	for(auto temp:sa)
		cout << temp << " ";
	cout << endl;

	cout << "ia: ";
	for(auto temp:ia)
		cout << temp << " ";
	cout << endl;

	cout << "sa2: ";
	for(auto temp:sa2)
		cout << temp << " ";
	cout << endl;
	
	cout << "ia2: ";
	for(auto temp:ia2)
		cout << temp << " ";
	cout << endl;
    return 0;
}

输出如下

sa:           
ia: 0 0 0 0 0 0 0 0 0 0 
sa2:           
ia2: 1268149248 32637 1268101768 32637 1268144288 32637 1268096928 32637 6 0 
3.28 数组和vector的区别?

1.数组定长:数组一旦定义,维度就是确定的。丧失了一些灵活性,但是软件运行效率在一定程度上得到增强。
2.数组没有size()成员函数
所以如果想获取长度,只能通过以下方式获取。
String数组:strlen函数

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    char a[10] = {'1', '1', '1', '1', '1', '1', '1', '1', '1', '1'};
	cout << strlen(a) << endl;
}

输出是13,为什么那?

其他数组:sizeof(aray_name)/sizeof(aray_name[0])

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    int a[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
	cout << sizeof(a) / sizeof(a[0] ) << endl;
}

3.5.2 访问数组元素

练习3.31-3.32
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int a[] = {0, 1, 2, 3, 4, 5};
    int b[5];
	for(int i  = 0; i < 5; i++)
	{
		b[i] = a[i];
	}
	for(int i  = 0; i < 5; i++)
	{
		cout << "b[" << i << "]: " << b[i] << endl;
	}
	
	vector<int> va = {0, 1, 2, 3, 4, 5};
	cout << "vector_a:";
	for(auto i: va)
	{
		cout << i << " ";
	}
	vector<int> vb(va);
	cout << endl << "vector_b:";
	for(auto i: vb)
	{
		cout << i << " ";
	}
    return 0;
}

3.5.3 指针与数组

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

PS1:就3.28练习题拓展说一下,有两种情况例外

PS2:指针占用几个地址?参考解释如下
32位系统是4个字节;64位系统就是8个字节
CPU和硬件直接通讯,只能通过内存与数据交互;
CPU通过地址总线、控制总线、数据总线与内存进行数据交互和操作
地址总线:代表寻址能力,比如32位系统就是32条地址线(0或者1)
控制总线:控制能力或者控制方式,比如读或者写
数据总线:数据传输速度,即单次数据传输量

练习3.5.3节练习
练习3.34:假定p1和p2指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序非法?

参考习题册答案:
作用:使p1由原来所指地址指向p2所指向的位置。
同一数组,或者数据类型相同的不同数组都是合法的;
数据类型不同的不同数组是非法的。

练习3.36:编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector是否相等。

PS:vector支持’!=',比较简单就不写了。数组array需要逐位比较,程序如下

#include <iostream>
using namespace std;

int main()
{
    int a[] = {1, 2, 3, 4};
	int b[] = {1, 2, 5, 6};
	// don't use &a[0],because &a[0] is a address and occupy 8 bytes in 64 bit computer.
	int size_a = sizeof(a)/sizeof(a[0]);  
	int size_b = sizeof(b)/sizeof(b[0]);
    cout << "sizeof(&a[0]):" << sizeof(&a[0]) << endl;
	cout << "sizeof(a):" << sizeof(a) << endl;
	cout << "sizeof(a[0]):" << sizeof(a[0]) << endl;
	cout << "the array a's size is:" << size_a << endl;
	if(size_a != size_b)
	{
		cout << "a's sieze:" << size_a << ", size_b:" << size_b << "different!" << endl;
	}
	else
	{
		for(int i = 0; i < size_a; i++)
	    {
			if(a[i] != b[i])
			{
				cout << "in the " << i << " postion, they are different! a'member:" << a[i] << ", b'member:" << b[i] << endl;
			    break;
			}
			else
			{
				cout << "in the " << i << " postion, a'member:" << a[i] << ", b'member:" << b[i] << endl;
			}
		}
	}
    cout << "Hello World";
    return 0;
}

3.5.4 C++风格字符串(字符串字面值’\0’)

C标准库string函数(cstring)

  • strlen(p)
    字符串字面值必须以’\0’结尾;
  • strlcmp(p1,p2)
大小返回值
p1>p2>0
p1==p2=0
p1<p2<0
  • strcat(p1, p2)
    将p2追加到p1,返回p1
  • strcpy(p1, p2)
    将p2拷贝给p1,返回p1
    strcpy示例如下
#include <iostream>
#include <cstring>

using namespace std;

int main()
{
	// remember: 'strcmp'and strcpy must compare or copy some objects end with '\0'
    char c1[] = "12345678";
	char c2[] = "456";
	strcpy(c1, c2);
	cout << "c1: " << endl;
	for(int i = 0; i < 8; i++)
	{
		cout << c1[i] << endl;
	}
	return 0;
}

输出如下:

c1: 
4
5
6

5
6
7
8

strcat用法如下:

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
	// remember: 'strcmp'and strcpy must compare or copy some objects end with '\0'
    char d1[] = "12345678";
	char d2[12] = "12345678";
	char d3[] = "456";
	strcat(d1, d3);
	cout << "d1.size(): " << sizeof(d1)/sizeof(char) << endl;
    strcat(d2, d3);
	cout << "d2.size():" << sizeof(d2)/sizeof(char) << endl;
	return 0;
}

输出如下

d1.size(): 9
d2.size():12

练习3.37:下面的程序有何含义,程序的输出结果是什么?
#include <iostream>
using namespace std;

int main()
{
	const char ca[] = {'h', 'e', 'l', 'l','o'};
	const char *cp = ca;
	while(*cp)
	{
		cout << *cp << endl;
		cp++;
	}
	return 0;
}

我使用菜鸟在线编辑器输出为 空 , 为什么那?
答:其实并非为空,应该是因为输出太快了,刷屏了;前几个字母是能输出出来的,但是指针不断向后移动,没有终点,所以除了输出空白,还可能输出一堆乱码出来!
数组名其实就是指向首元素的指针,不过指针不知道边界,需要程序猿考虑周全(将范围考虑进去)
修改后的程序如下(可输出正确结果):

#include <iostream>
using namespace std;

int main()
{
	const char ca[] = {'h', 'e', 'l', 'l','o'};
	const char *cp = ca;
	int i = 0;
	while(i < 5 )
	{
		cout << *cp << endl;
		cp++;
		i++;
	}
	return 0;
}

输出为

h
e
l
l
o
练习3.38:两个指针相加不但非法,而且没有意义,请问为什么没有意义?

答:老王家的门牌号+老李的门牌号=谁家的门牌号那? (母鸡,所以没意义)
再比如每家是按照门牌号排列,那么
老王家的门牌号-老李的门牌号=门牌号差值(可能就是两家的距离)
所以两个指针相加无意义,指针相减才有意义

练习3.39:编写程序,比较两个string对象的;再编写一段程序,比较两个C风格字符串内容。

比较两个string

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string sa = "123456";
	string sb = "1239";
	cout << "sa: " << sa << endl;
	cout << "sb: " << sb << endl;
	if(sa < sb)
	{
		cout << "sa < sb" << endl;
	}
	else if(sa > sb)
	{
		cout << "sa > sb" << endl;
	}
	else
	{
		cout << "sa = sb" << endl;
	}
	return 0;
}

比较两个C风格的字符串

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
	// remember: 'strcmp' must compare some object end with '\0'! 
	// otherwise the result maybe not your respect!
    const char c1[] = "123";
	const char c2[] = {'1', '2', '\0'};
	const char c3[] = {'1', '2', '3', '\0'};
	int com_12 = strcmp(c1, c2);
	int com_13 = strcmp(c1, c3);
	cout << "com_12: " << com_12 << endl<< "com_13:" << com_13 << endl;
	cout << "c1_size: " << sizeof(c1)/sizeof(char) << endl; 
	cout << "c2_size: " << sizeof(c2)/sizeof(char) << endl; 
	cout << "c3_size: " << sizeof(c3)/sizeof(char) << endl; 
	return 0;
}

输出如下:

com_12: 51
com_13:0
c1_size: 4
c2_size: 3
c3_size: 4
练习3.40:编写程序,定义两个字符数组并用字符串字面值初始化它们;接着定义一个字符串数组存放前两个数组连接后的结果。使用strcpy和strcat把前两个数组的内容拷贝到第三个数组中。
#include <iostream>
#include <cstring>

using namespace std;

int main()
{
	// remember: 'strcmp'and strcpy must compare or copy some objects end with '\0'
    char c1[] = "123";
	char c2[] = "456";
	char c3[8] = {};
	strcat(c1, c2);
	strcpy(c3, c1);
	cout << "c3: " << endl;
	for(int i = 0; i < 20; i++)
	{
		cout << c3[i] << endl;
	}

	return 0;
}

输出如下(what???)

c3: 
1
2
3
4
5
6


4
5
6

1
2
3
4







3.5.5 与旧代码的接口

初始化
.c_sr
C风格字符串
string

使用整型数组初始化vector对象

  • vector ivec(begin(int_arr),end(int_arr))
  • vector ivec(int_arr+2, int_arr+5) //使用int_arr下标为2,3,4元素初始化ivec.

3.6 多维数组

  • C++中没有多维数组,所谓多维数组就是数组的数组

3.6.1 多位数组初始化

  • 方式1:初始化每一行的第一个元素;
  • 方式2:初始化第一行元素,其他元素被初始化为0(省略内层括号的初始化方式);
int ia[3][4] = {{0},{4},{8}};
int ix[3][4] = {0, 3, 6, 9};

3.6.2 多位数组的下标引用

下标运算符数量和数组的维度

  • 相等:表达式结果将是给定类型的元素。
  • 不等:表达式结果将是给定索引处的一个内层数组。
    两层嵌套for循环处理多维数组
#include <iostream>
using namespace std;

int main()
{
    constexpr size_t rowCnt = 3, colCnt = 4;
	int ia[rowCnt][colCnt];
	for (size_t i = 0; i != rowCnt; i++)
	{
		for (size_t j = 0; j != colCnt; j++)
		{
			ia[i][j] = rowCnt*i + j;
		}
	}
	for (size_t i = 0; i != rowCnt; i++)
	{
		for (size_t j = 0; j != colCnt; j++)
		{
			cout << "ia[" << i << "][" << j << "]: "<< ia[i][j] << " ";
		}
		cout << endl;
	}
    return 0;
}

输出如下

ia[0][0]: 0 ia[0][1]: 1 ia[0][2]: 2 ia[0][3]: 3 
ia[1][0]: 3 ia[1][1]: 4 ia[1][2]: 5 ia[1][3]: 6 
ia[2][0]: 6 ia[2][1]: 7 ia[2][2]: 8 ia[2][3]: 9 

3.6.3 使用范围for循环处理多维数组

-使用for(新特性?)循环处理多维数组,除了最内层循环外,其他所有循环控制变量都应该使用引用类型
否则如下如果初始化row时,row是指向数组首元素的指针,类型是int *,内层循环不合法(我试了下,内部解引用好像也会报错)。

#include <iostream>
using namespace std;

int main()
{
    constexpr size_t rowCnt = 3, colCnt = 4;
	int ia[rowCnt][colCnt];
	size_t cnt = 0;
	for (auto &row : ia)
	{
        for(auto &col : row)
		{
			col = cnt;
			cnt++;
		}
	}
	for (auto &row : ia)
	{
        for(auto &col : row)
		{
			cout << col << " ";
		}
		cout << endl;
	}
    return 0;
}

输出如下

0 1 2 3 
4 5 6 7 
8 9 10 11

3.6.4 指针与多维数组

  • 对于上面那个例子,你说,我就是不想用多维数组中使用引用,那我该如何做那?
    这样做(需要使用新特性for循环了,使用普通for循环+begin和end函数)
	for (auto row = begin(ia); row != end(ia); row++)
	{
        for(auto col = begin(*row); col != end(*row); col++)
		{
			cout << *col << " ";
		}
		cout << endl;
	}
  • 多维数组名–>指向第一个内层数组的指针;
int ia[3][4];
int (*p)[4] = ia;  //等价于int (*p)4 = &ia[0], p是指针,指向包含四个整数的数组
p = &ia[2]    // p指向ia的尾元素

上述圆括号必不可少

int *p[4];  // p是一个数组,指向四个整型指针
int (*p)[4];   // p是一个指针,指向包含四个整数的数组

3.6.5 类型别名 简化 多维数组的指针

using int_array = int[4];
typedef int int_array[4];  //4个整f数组成的数组
for(int_array *p = ia;  p != ia + 3; p++)
{
    for(int *q = *p; q != *p + 4; q++ ) // p 是&ia[m],*p 是&(int[4])即内层第一个元素的地址
    {
        cout << *q << ' ';
    }
    cout << endl;
}

1.int p 和 int p 区别
答:没什么区别,编译器处理结果相同
(1)C语言更强调语法:int *p强调p是一个int型的变量。
(2)C++ 更强调类型:int* p 是一个指向int的指针。
** 2.int * p=&a;和p=&a;区别**
是一样的;
第一种只是声明他是一个指针变量,而并不是取出所指向地址中的值,注意其与
p = a;这里是将a的值赋予指针p所指向的一个变量。

** 3.几种内存区域的划分**
全局区/静态区:放到函数之外的变量(main函数中的变量也是局部变量)
程序开始开辟,结束时才释放

常量存储区:const int a = 5;(符号常量)、“hello world”(字面常量)
信息不可修改

:new/delete的变量、以及malloc和free的变量
(程序员自动释放分配)
另外多说一句,用new创建的对象是动态对象。

: int a;(局部变量)
(编译器自动分配释放)

4 表达式

  • 一个 或 多个运算对象组成;

4.1 基础

  • 左值(ell value): 用的是对象身份(内存中位置);
    左值举例说明:赋值运算符左侧对象;解引用、下标运算符、递增递减符;
    我感觉充当左值条件判断是:这个东西是否可以继续取地址;
  • 右值(are value):用的是对象的值;
    右值举例说明:取地址符&,,得到一个指针;

7 类

7.2 访问控制与封装

访问修饰符
类成员访问继承类成员访问外部访问
1.对象访问
public
protect×
private××

注“如何理解类的外部”:不在类声明和定义的地方?

使用struct和class定义类的唯一区别:默认访问权限(定义在第一个访问说明符之前成员)
  • Struct: public
  • Class: private
7.16 什么样的成员应该定义在private说明符之后?

答:不想被类外部代码知道的数据;

7.18 封装有何含义?它有什么用处?

答:只关心接口即可,不需要关心内部的实现

7.3 类的其他特性

7.2.1 友元

7.3 类的其他特性

7.4 类的作用域

7.5 构造函数再探

7.6 类的静态成员

15 类

15.5 访问控制与继承

15.6 继承中的类作用域

关于隐藏、覆盖(重写)、重载的理解

  • 在派生-对象中:优先考虑隐藏,此时派生类中的覆盖(重写)也是隐藏;没有隐藏的情况下,子类对象才能调用父类重载函数。[此时感觉virtual没用,]
  • 在派生-指针或者引用中:只用覆盖(重写)和重载;
    注:C++ Primmer P550解释为:D1的fnc并没有覆盖(重写)Base的虚函数fnc…实际上,D1…此时拥有了两个名为fnc的函数:一个是D1从Base继承而来的(隐藏的)虚函数fnc;另一个是D1自己定义的接受的接受int参数的非虚函数fnc。

其他有利于理解的说法:如果派生类定义了一个与基类成员函数同名的函数(无论参数列表是否相同),那么派生类中的这个函数将隐藏基类中的所有同名函数(隐藏仅仅指类的对象,对于指向派生类的基类指针仍然可以调用其他虚函数)

下面是我写的两个函数示例(后续优化)

/*
* 知识点:隐藏、覆盖(重写)、重载
* One:对于隐藏、覆盖(重写)
*     在派生-对象中:优先考虑隐藏,此时派生类中的覆盖(重写)也是隐藏;没有隐藏的情况下,子类对象才能调用父类重载函数。[此时感觉virtual没用,]
*     在派生-指针或者引用中:只用覆盖(重写)和重载;
*
*
其他有利于理解的说法
* 如果派生类定义了一个与基类成员函数同名的函数(无论参数列表是否相同),
* 那么派生类中的这个函数将隐藏基类中的所有同名函数(隐藏仅仅指类的对象,
* 对于指向派生类的基类指针仍然可以调用其他虚函数)
*/

#include <iostream>
using namespace std;

class Base
{
public:
	Base(int a) : m_v(a) {}
	virtual void setM() { m_v = 0; }
	virtual void setM(int a) { m_v = a; }
	virtual int getM() { return m_v; }
protected:
	int m_v;
};

class Drive : public Base
{
public:
	Drive() : Base(42) {}
	void setM() override { m_v = 1000; } // 使用 override 关键字明确这是一个重写的方法
};

class DDrive : public Drive
{
public:
	DDrive() : Drive() {}
};

int main()
{
	Base b(6);
	cout << "Base: " << b.getM() << endl;  // 输出 6
	b.setM();
	cout << "Base setM() default(0): " << b.getM() << endl;  // 输出 0,因为调用了 Base 的 setM()
	b.setM(9);
	cout << "Base setM(9): " << b.getM() << endl;  // 输出 9
	cout << endl;

	Drive d;
	cout << "Drive Base default(): " << d.getM() << endl; // 输出 42,因为 Drive 的构造函数调用了 Base(42)
	d.setM();
	cout << "Drive setM() default(0): : " << d.getM() << endl; // 输出 1000,因为调用了 Drive 的 setM()
	//d.setM(1001);   // Error:函数调用参数过多;派生类对象对父类进行隐藏
	//cout << "Drive (1001) : " << d.getM() << endl; // 输出 1000,因为调用了 Drive 的 setM()

	// 以下部分展示了多态性
	Base* pb = &b;
	cout << "Base: " << pb->getM() << endl;  // 输出 9
	pb->setM();
	cout << "Base: " << pb->getM() << endl;  // 输出 0,因为 pb 指向 Base 对象,调用了 Base 的 setM()
	pb->setM(9);
	cout << "Base: " << pb->getM() << endl;  // 输出 9

	Base* pd = &d;
	cout << "Drive: " << pd->getM() << endl; // 输出 1000
	pd->setM();
	cout << "Drive: " << pd->getM() << endl; // 输出 1000,因为 pd 指向 Drive 对象,调用了 Drive 的 setM()
	pd->setM(1001);
	cout << "Drive: " << pd->getM() << endl; // 输出 1001,因为调用了 Drive 的 setM(int)

	// 延申:子类的不恰当隐藏会影响到孙类"!!!对象!!!",但并不影响孙类指针
	DDrive dd;
	cout << "Drive Base default(): " << dd.getM() << endl;
	dd.setM();
	cout << "Drive setM() default(0): : " << dd.getM() << endl;
	//dd.setM(1001);    // Error:函数调用参数过多;子类的不恰当隐藏会影响到孙类!!!对象!!!
	cout << "Drive (1001) : " << dd.getM() << endl;

	Base* pdd = &dd;
	cout << "Drive: " << pdd->getM() << endl;
	pdd->setM();
	cout << "Drive: " << pdd->getM() << endl;
	pdd->setM(1001);
	cout << "Drive: " << pdd->getM() << endl;

	return 0;
}

还有个更长的示例

#include <iostream>
using namespace std;

/*
*一、
*在C++中,重写(Override)和隐藏(Hiding)是两个不同的概念,它们在某些情况下可能会产生冲突或混淆,尤其是在涉及继承时。
但是,这两个概念本身并不直接冲突,而是需要开发者明确理解和区分它们以避免潜在的问题。

- 重写(Override)
重写发生在子类中,当子类提供了一个与基类中的虚函数具有相同签名(即相同的函数名、返回类型、参数列表)的成员函数时。
这允许子类改变从基类继承的虚函数的行为。重写是动态绑定的一部分,因此当通过基类指针或引用调用重写的虚函数时,将调用子类中的版本(如果指针或引用实际上指向子类对象)。

- 隐藏(Hiding)
隐藏也发生在子类中,但它通常与名称冲突相关。当子类提供了一个与基类中的非虚函数具有相同名称的成员函数时,基类的该函数在子类中将被隐藏。
这意味着在子类的**对象**直接调用该函数时,将调用子类中的版本,而不是基类中的版本。
但是,如果通过基类指针或引用调用该函数(即使该指针或引用实际上指向子类对象),仍然会调用基类中的版本(除非基类中的函数也是虚函数)。

*冲突和混淆
- 重写和隐藏之间的主要混淆在于它们都与函数名和继承有关,但行为却截然不同。如果开发者不清楚何时发生重写、何时发生隐藏,可能会导致意外的行为。
- 此外,如果基类中的函数被设计为虚函数(以支持多态),但子类中的同名函数没有被声明为override(在C++11及更高版本中支持),并且基类中的函数签名在子类中发生了更改(例如参数数量或类型不同),
那么子类中的函数实际上将隐藏基类中的虚函数,而不是重写它。
这可能导致代码中的错误,因为开发者可能期望实现多态行为,但实际上却得到了隐藏行为。

为了避免这种混淆,建议:
- 在子类中重写基类的虚函数时,使用override关键字(如果编译器支持)。这将使编译器在基类中没有匹配的虚函数时发出错误。
- 尽量避免在子类中隐藏基类中的非虚函数,除非这是有意为之。如果必须这样做,请确保在文档和注释中清楚地说明这一点。
- 理解多态、重写和隐藏的概念,并始终注意在涉及继承时的函数签名和访问级别。

*二、如果你遇到d2.doSomething(3.14)不能调用Base类的doSomething(double a)方法的情况,可能有几个原因:

①隐藏基类方法:如果在Derived2或它的直接基类Derived中有任何重载版本的doSomething方法,并且没有使用using声明来引入基类的方法,基类的方法可能会被隐藏。
这意味着,即使基类中存在doSomething(double a),如果派生类中定义了其他版本的doSomething(如doSomething(int)或doSomething(char)),
但没有显式地引用基类的方法,基类版本的方法在派生类对象上可能无法直接访问。
②名称隐藏:在C++中,如果派生类中定义了与基类相同名称的成员函数,即使参数列表不同,基类中的同名函数也会被隐藏。
这是C++的名称隐藏规则,不同于重载。
③访问控制:如果Base类的doSomething方法是protected或private,则不能在Derived2类的对象上直接调用它(尽管这种情况不太可能,因为你提到了virtual,这通常意味着方法应该是public的)。



另一方面,dp2->doSomething(3.14);可以工作,可能是因为dp2是一个指向Derived2的指针,但是被当做Base类的指针来使用。
当你通过基类指针调用一个virtual函数时,C++的多态性确保调用的是指针实际指向的对象的最具体的实现(即动态绑定)。
如果Derived2没有重载doSomething(double a),则调用会回退到Base类的实现。

如果d2.doSomething(3.14)不工作,而dp2->doSomething(3.14);工作,最可能的原因是名称隐藏。
在派生类中定义新的doSomething函数时,如果没有正确地使用using声明来引入基类的函数,基类的同名函数会被隐藏
*/

class Base {
public:
    virtual void doSomething(int a) {
        // 基类的doSomething(int)方法  
        std::cout << "Base::doSomething(int) called with " << a << std::endl;
    }

    virtual void doSomething(char a) {
        // 基类的doSomething(double)重载方法  
        std::cout << "Base::doSomething(char) called with " << a << std::endl;
    }

    virtual void doSomething(int a, int b, int c) {
        // 基类的doSomething(double)重载方法  
        std::cout << "Base::doSomething(int a,int b, int c) called with a:" << a << ", b:" << b << ", c:" << c << std::endl;
    }


    void doSomething(int a, int b, int c, int d) {
        // 基类的doSomething(double)重载方法  
        std::cout << "Base::doSomething(int a, int b, int c, int d) called with a:" << a << ", b:" << b << ", c:" << c << ", d:" << d << std::endl;
    }

    void coutName() {
        // 基类的coutName重载方法  
        std::cout << "Base::coutName" << std::endl;
    }
};

class Derived1 : public Base {
public:
    //using Base::doSomething;
    void doSomething(int a) {
        // 派生类重写了基类的doSomething(int)方法  
        std::cout << "Derived1::doSomething(int) called with " << a << std::endl;
    }

    //using Base::doSomething;
    void doSomething(char c) {
        // 派生类重写了基类的doSomething(char c)方法  
        std::cout << "Derived1::doSomething(char) called with " << c << std::endl;
    }
};

class Derived2 : public Base {
public:
    //using Base::doSomething;
    void doSomething(int a) {
        // 派生类重写了基类的doSomething(int)方法  
        std::cout << "Derived2::doSomething(int) called with " << a << std::endl;
    }

    virtual void doSomething(int a, int b, int c, int d) {
        // 派生类重写了基类的doSomething(int a, int b, int c, int d)方法  
        std::cout << "Derived2::doSomething(double) called with a:" << a << ", b:" << b << ", c:" << c << ", d:" << d << std::endl;
    }
};

class Derived3 : public Base {
public:
    //using Base::doSomething;
    void doSomething(int a) {
        // 派生类重写了基类的doSomething(int)方法  
        std::cout << "Derived3::doSomething(int) called with " << a << std::endl;
    }

    //using Base::doSomething;
    void doSomething(int x, int y) {
        // 派生类重载了doSomething(int x, int y)方法
        std::cout << "Derived3::doSomething(int x, int y) called with x:" << x << ",y:" << y << std::endl;
    }
};


class Derived11 : public Derived1 {
public:
    //using Base::doSomething;
    void doSomething(int a) {
        // 派生类重写了基类的doSomething(int)方法  
        std::cout << "Derived11::doSomething(int) called with " << a << std::endl;
    }
};

class Derived21 : public Derived2 {
public:
    //using Base::doSomething;
    void doSomething(int a) {
        // 派生类重写了基类的doSomething(int)方法  
        std::cout << "Derived21::doSomething(int) called with " << a << std::endl;
    }
};

class Derived31 : public Derived3 {
public:
    //using Base::doSomething;
    void doSomething(int a) {
        // 派生类重写了基类的doSomething(int)方法  
        std::cout << "Derived31::doSomething(int) called with " << a << std::endl;
    }
};

int main() {
    cout << "************************************实例化对象开始表演******************************************************" << endl;
    Base b1;
    b1.doSomething(42);
    //b1.doSomething(3.14);      //  !!!编译报错提示"有多个重载函数 实例与参数列表相同,26和31行",因为3.14不知道转换为int,还是char
    b1.doSomething('c');
    b1.doSomething(1, 2, 3);
    b1.doSomething(1, 2, 3, 4);
    b1.coutName();

    cout << endl << endl;

    Derived1 d1;
    d1.doSomething(42);            // 调用Derived1的doSomething(int)  
    //d1.doSomething(3.14);          // !!!编译报错提示"有多个重载函数 实例与参数列表相同,56和62行",因为3.14不知道转换为int,还是char
    d1.doSomething('c');           // !!!'c'转化内int
    //d1.doSomething(1, 2, 3);     // !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int)"
    //d1.doSomething(1, 2, 3, 4);  // !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int, int)"
    d1.coutName();                 // 调用基类的doSomething(double),因为派生类没有重写它  

    cout << endl << endl;
    Derived2 d2;
    d2.doSomething(42);    // 调用Derived2的doSomething(int)  
    d2.doSomething(3.14);  // 3.14转换为int,调用相同
    d2.doSomething('c');   // 'c'转化内int
    //d2.doSomething(1, 2, 3);//!!!报错提示"没有与参数列表匹配的 重载函数Derived2::doSomething, 参数类型为(int, int, int)"
    d2.doSomething(1, 2, 3, 4); //!!!这是Derived2自身的函数,与Base没有关系
    d2.coutName();         // 调用基类的doSomething(double),因为派生类没有重写它 

    // !!!子类Derived3::doSomething(int x, int y),是否对父类方法进行隐藏。也就是说d3是否拥有doSomething(double a)???
    cout << endl << endl;
    Derived3 d3;
    d3.doSomething(42);    // 调用Derived2的doSomething(int)  
    d3.doSomething(3.14);  // 3.14转换为int,调用相同
    d3.doSomething('c');   // 'c'转化内int
    //d3.doSomething(1, 2, 3);     // !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int)"
    //d3.doSomething(1, 2, 3, 4);  // !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int, int)"
    d3.doSomething(1, 2);   //!!!这是Derived3自身的函数,与Base没有关系
    d3.coutName();         // 调用基类的doSomething(double),因为派生类没有重写它 
    cout << endl << endl;

    cout << "******************创建Derived1、Derived2、Derived3单独的指针(感觉类实例化对象用法一致)*********************************************" << endl;
    Derived1* opd1 = new Derived1;
    opd1->doSomething(42);    // 调用Derived1::doSomething(int)  
    opd1->doSomething('c');   // 调用Derived1::doSomething(char)
    //opd1->doSomething(1, 2, 3); // !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int)"
    //opd1->doSomething(1, 2, 3, 4);// !!!报错提示"没有与参数列表匹配的 重载函数Derived1::doSomething, 参数类型为(int, int, int, int)"
    opd1->coutName();

    cout << endl << endl;
    Derived2* opd2 = new Derived2;
    opd2->doSomething(42);    // 调用Derived2的doSomething(int)  
    opd2->doSomething('c');   // 调用Base::doSomething(char)
    //opd2->doSomething(1, 2, 3);//!!!报错提示"没有与参数列表匹配的 重载函数Derived2::doSomething, 参数类型为(int, int, int)"
    opd2->doSomething(1, 2, 3, 4); // !!!这是Derived2自身的函数,与Base没有关系
    opd2->coutName();

    cout << endl << endl;
    Derived3* opd3 = new Derived3;
    opd3->doSomething(42);    // 调用Derived2的doSomething(int)  
    opd3->doSomething('c');   // 'c'转化内int
    //opd3->doSomething(1, 2, 3);// !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int)"
    //opd3->doSomething(1, 2, 3, 4); // !!!报错提示"没有与参数列表匹配的 重载函数Derived3::doSomething, 参数类型为(int, int, int, int)"
    opd3->doSomething(1, 2);   // !!!报错提示"没有与参数列表匹配的 重载函数Base::doSomething, 参数类型为(int, int)"
    opd3->coutName();
    cout << endl << endl;

    cout << "******************创建基类指针分别指向Derived1、Derived2、Derived3(virtual在派生类中多态应用)**************************************" << endl;
    Base* pd1 = new Derived1;
    pd1->doSomething(42);    // 调用Derived1::doSomething(int)  
    pd1->doSomething('c');   // 调用Derived1::doSomething(char)
    pd1->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
    pd1->doSomething(1, 2, 3, 4);// 调用Base::doSomething(int, int, int, int)
    pd1->coutName();

    cout << endl << endl;
    Base* pd2 = new Derived2;
    pd2->doSomething(42);    // 调用Derived2的doSomething(int)  
    pd2->doSomething('c');   // 调用Base::doSomething(char)
    pd2->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
    pd2->doSomething(1, 2, 3, 4); // !!! 这里调用基类Base::doSomething(int, int, int, int),因为不是虚函数,子类Derived2没有对其重写
    pd2->coutName();

    cout << endl << endl;
    Base* pd3 = new Derived3;
    pd3->doSomething(42);    // 调用Derived2的doSomething(int)  
    pd3->doSomething('c');   // 'c'转化内int
    pd3->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
    pd3->doSomething(1, 2, 3, 4);// 调用Base::doSomething(int, int, int, int)
    //pd3->doSomething(1, 2);   // !!!报错提示"没有与参数列表匹配的 重载函数Base::doSomething, 参数类型为(int, int)"
    pd3->coutName();
    cout << endl << endl;

    cout << "******************创建Derived11、Derived21、Derived31单独的指针(感觉类实例化对象用法一致)*********************************************" << endl;
    Derived11* opd11 = new Derived11;
    opd11->doSomething(42);    // 调用Derived11::doSomething(int)  
    opd11->doSomething('c');  // 'c'转化内int
    //opd11->doSomething(1, 2, 3); // !!!报错提示"参数太多",我感觉跟原来哪个意思一样("没有与参数列表匹配的 重载函数Derived11::doSomething, 参数类型为(int, int, int)")
    //opd11->doSomething(1, 2, 3, 4);// !!!报错提示"参数太多"
    opd11->coutName();

    cout << endl << endl;
    Derived21* opd21 = new Derived21;
    opd21->doSomething(42);    // 调用Derived21的doSomething(int)  
    opd21->doSomething('c');   // 'c'转化内int
    //opd21->doSomething(1, 2, 3);// !!!报错提示"参数太多"
    //opd21->doSomething(1, 2, 3, 4); // !!!报错提示"参数太多"
    opd21->coutName();

    cout << endl << endl;
    Derived31* opd31 = new Derived31;
    opd31->doSomething(42);    // 调用Derived2的doSomething(int)  
    opd31->doSomething('c');   // 'c'转化内int
    //opd31->doSomething(1, 2, 3);;// !!!报错提示"参数太多"
    //opd31->doSomething(1, 2, 3, 4);;// !!!报错提示"参数太多"
    //opd31->doSomething(1, 2); ;// !!!报错提示"参数太多"
    opd31->coutName();
    cout << endl << endl;

    cout << "******************创建基类指针分别指向Derived11、Derived21、Derived31(virtual在派生类中多态应用)**************************************" << endl;
    Base *pd11 = new Derived11;
    pd11->doSomething(42);    // 调用Derived1::doSomething(int)  
    pd11->doSomething('c');   // 调用Derived1::doSomething(char)
    pd11->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
    pd11->doSomething(1, 2, 3, 4);// 调用Base::doSomething(int, int, int, int)
    pd11->coutName();

    cout << endl << endl;
    Base *pd21 = new Derived21;
    pd21->doSomething(42);    // 调用Derived2的doSomething(int)  
    pd21->doSomething('c');   // 调用Base::doSomething(char)
    pd21->doSomething(1, 2, 3);// 调用Base::doSomething(int, int, int)
    pd21->doSomething(1, 2, 3, 4); // !!! 这里调用基类Base::doSomething(int, int, int, int),因为不是虚函数,子类Derived2没有对其重写
    pd21->coutName();

    cout << endl << endl;
    Base* pd31 = new Derived31;
    pd31->doSomething(42);    // 调用Derived2的doSomething(int)  
    pd31->doSomething('c');   // 'c'转化内int
    pd31->doSomething(1, 2, 3);// !!!报错提示"参数太多"
    pd31->doSomething(1, 2, 3, 4);// !!!报错提示"参数太多"
    //pd31->doSomething(1, 2);  // !!!报错提示"参数太多"
    pd31->coutName();
    cout << endl << endl;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值