2 变量与基本类型
char | short | int | long | float | double | 指针 | |
---|---|---|---|---|---|---|---|
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) |
字数 | 一个字 | 一个字 | 两个字 | ||||
有效位 | 0 | 7 | 16 | ||||
最大值 | 42 9496 7296 | **** |
注:内存Buffer运算一般以字节为单位;上述字数转换使用一个字=四个字节
2.0 float与Double 精度与有效位
符号位s | 指数位E 是有符号的 比较特殊,需要移位 | 尾数位M 只是小数点后面的位数哈 | |
---|---|---|---|
float | 1 | 8 (-127~128) | 23 |
double | 1 | 11 (-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
Binary | 00000000 | 00000001 | … | 01111111 | 10000000 | 10000001 | … | 11111100 | 11111110 | 11111111 |
signed | 0 | 1 | … | 127 | -128 | -127 | … | -3 | -2 | -1 |
unsigned | 0 | 1 | … | 127 | 128 | 129 | … | 253 | 254 | 255 |
十进制 | 0 | 1 | 2 | 3 | … | 127 | -128 | -127 | … | -3 | -2 | -1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
原码 | 00000000 | 00000001 | 00000002 | 00000003 | … | 01111111 | 10000000 | 11111111 | … | 10000011 | 10000010 | 10000001 |
反码 (除符号位取反) | 00000000 | 00000001 | 00000002 | 00000003 | … | 01111111 | 11111111 | 10000000 | … | 111111100 | 11111101 | 11111110 |
补码 (反码+1) | 00000000 | 00000001 | 00000002 | 00000003 | … | 01111111 | 10000000 | 10000001 | … | 11111101 | 11111110 | 11111111 |
unsigned | 0 | 1 | 2 | 3 | … | 127 | 128 | 129 | … | 253 | 254 | 255 |
- 关于超出范围的实际结果
- 带符号类型:结果未定义
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)
- 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 字面值常量
- 整型和浮点型字面值
20 /* 十进制 /
024 / 八进制 /
0x14 / 十六进制 */
浮点型:一个小数、或以科学计数法表示的指数(指数部分用E或e标识)
3.14159
0e0
.001 - 字符字面值、字符串字面值
- 转义字符:
无法打印的字符:\n、\t、\V
特殊含义的字符:单引号、双引号、问号、反斜线
’
"
?
\ - 指定字面值类型
-
(前缀)字符、字符串字面值
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 变量定义(分号结束)——类型说明符+变量名列表;
- 何为对象?
- 具有某种类型的内存空间
- 其他分类:类与对象、对象与未命名对象、对象与只读数据
- 初始值:对象在 创建 时获得了一个特定的值。
- 注: 初始化不是赋值,赋值是把对象当前值擦除、以一个新值替代。
- 列表初始化 int a{ld};
- 注,列表初始化好处:初始值存在丢失风险时,编译器将报错。
实例如下:
long double ld = 3.1415926536;
int a{ld}, b = {ld} // 错误,转换未执行,因为错在丢失风险
int c(ld), d = ld; // 正确,转换执行,且确实丢失了部分值
- 默认值初始化:定义变量时没有指定初始值
- 在任何函数体之外:变量被初始化为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 = π // 指向常量的常量指针
注:此处指向常量和常量指针定义采用C++ Primmer,可能与流程说法不同。
2.4.3 顶层const
- 指针、引用与const
// examlpe
const int ci =42; // 不能改变ci的值,这是一个顶层const
consdt int *p2 = π // 允许改变p2的值,这是一个底层const
const int *const p3 = p2; // 左边是底层const(指针所指对象是一个常量)
// 右边是顶层const (指针对象是一个常量)
const int &r =ci; // 用于声明的的const都是底层const
注:
- 如果按照上面那个图理解,引用这里有点特殊,需要着重记一下;
- 底层应用目前仅见过复合类型的指针和引用中。
- 执行拷贝时:顶层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)
- 类型别名(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;
- 3.6.5节 类型别名 对 多维数组的指针
- 类型别名与函数指针
typedef char (*p)(int); // 声明函数指针
p pFun; // 创建函数指针对象;
char glFun(int a){ return;}
void main()
{
pFun = glFun; // 初始化函数指针对象;
(*pFun)(2);
}
2.5.2 auto类型说明符
- 引用作为初始值时:真正参与初始化的是引用对象的值,引用对象的类型作为auto的类型。
int i = 0, &r = i;
auto a = r; // a是一个整数(r是i的别名,而i是一个整数)
- 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)
- 若希望推断出的auto类型是一个顶层const,需明确指出:
const auto f = ci; // ci的推演类型是int,f是const int
- 可将引用的类型设为auto
auto &g = ci; // g是一个整型常量的引用,绑定到ci
auto &h = 42; // 错误,不能将非常量引用绑定到字面值
const auto &j = 42; // 可以为常量引用绑定字面值
- 一条语句中定义多个变量,初始值必须是同一种类型(&和*只从属于声明符,而非基本数据类型的一部分)
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类型指示符
- 目的:只想用表达式的类型,不想用表达式的值;
- 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。
- decltype((variable)),双括号永远是引用。
2.6定义自己的数据结构
- 数据结构:将相关数据元素组织在一起;然后使用它们的策略和方法。
2.6.1定义Scales_data类型
- struct和class有什么不同?(默认访问权限不同)
- 类的定义不要忘记加分号;
因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少。(可以类比普通变量的定义)
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;
注:最好将类定义和对象定义分开,所以第二种方式较好。
- 类内初始化:创建对象时,用于初始化数据成员。没有执行初始化的成员执行默认初始化。
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练习题拓展说一下,有两种情况例外
- sizeof(数组名):返回数组长度(数组所占字节数,而不是元素个数)
- &数组名:产生一个指向数组的指针,而不是一个指向某个指针常量的指针
以上引自《C和指针》P141~142
sizeof(数组名)中,数组名不就是一个地址吗?为什么可以得到数组长度那?以下是链接和解释:
sizeof是预编译指令,在预编译阶段已经替换。所以不仅仅是传递地址,还将该数组的特性(数组类型、元素个数)一并替换
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 与旧代码的接口
使用整型数组初始化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 = ⅆ
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;
}