-
Notifications
You must be signed in to change notification settings - Fork 0
/
Cpp Review
1864 lines (1422 loc) · 83.1 KB
/
Cpp Review
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
第一章
c++特点:1.支持抽象数据类型 2.多态性 3.继承性
c++程序组成:由一个函数或多个函数构成,并且只有一个主函数main 是程序执行的入口
程序设计流程:定义要解决的问题-确定如何解决问题-编写程序-编译源代码-连接目标文件和库-调试-测试
2.1数据类型
基本:布尔型、整型、字符型、浮点型、空类型
复合:指针类型 数组类型 枚举类型 联合类型 结构类型 类类型
char:字符型数据通常占用一个字节存储空间
整数也可以隐式的转换成 bool 值:非 0的整数转换成 true,0 转换成 false。
float通常4个字节 double通常八个字节
没有void类型的对象
unsigned和signed只用来修饰char和int,signed可以省略。对于有符号和无符号的整型数据,它们所占的存储空间的大小相同,但它们的表数范围却不相同(对字符型意义相同)。
结构体:不同的数据类型构成的一种混合的数据结构
共用体:类似于结构体的一种构造类型,构成共同体的数据成员共用同一段内存单元
指针类型:指针类型变量用于存储另一变量的地址,而不能用来存放基本类型的数据,在内存中占据一个存储单元。
2.2变量与常量
变量特征:变量类型、变量名、变量值
命名:1.关键字不可 2.下划线开头的名字是保留给实现或者运行环境,用于特殊目的,在程序中欧冠不要采用这样的名字3.大小写区分
4.见名知意 5.维持统一的命名风格
typedef:为某种类型声明一个新名字,而不是定义一种新类型 typedef <已有类型名> <新类型名>
常量
字符常量:用单括号引起的一个字符
字符串常量:由一对双引号括起来的零个或多个字符序列
字符串可以写在多行上,不过在这种情况下必须用反斜线‘\’表示下一行字符是这一行字符的延续。
字符串常量实际上是一个字符数组,组成数组的字符除显示给出的外,还包括字符结尾处标识字符串结束的符号‘\0’,所以字符串“abc”实际上包含 4 个字符:‘a’、‘b’、‘c’和‘\0’。
整型常量:可以用十进制八进制或十六进制表示
十进制常量 :一般占一个机器字长,是一个带正负号的常数(默认情况下为正数)。
八进制常量 :由数字 0 开头,其后由若干 0-7 的数字组成,如 0378,0123 等。
十六进制常量:以 0x 或 0X 开头,其后由若干 0-9 的数字及 A-F(或小写 a-f)的字母组成,如 0x123,0x3ab。
浮点型常量:有小数表示法和指数表示法
如11.3 .2 2.3e10 -2.3E-1等等
枚举常量:
声明形式:enum <枚举名>{<枚举符表>};
枚举符可以有两种形式:<枚举符名>/<枚举符名>=<整型常量>
符号常量:定义形式:const <类型名> <符号常量名>=<表达式>[,...];
[]表示可选项,以下都采用这种标记
定义的符号常量必须初始化,一个符号常量可看做是一个只读变量
2.3运算符 表达式
(type)强制类型转换运算符 (int)x
流读取运算符>>表示流的输入,可以从cin输入流中读取字符;流插入运算符<<表示流的输出,可以向cout输出流插入字符
<iostream>运算符
dex:十进制 hex:十六进制 oct:八进制
setfill(c)设填充字符为c setprecision(n)设显示小数精度为n位
setw(n)设域宽为n个字符
<iomanip>头文件控制符
setiosflags(ios::fixed)固定的浮点显示
setiosflags(ios::scientific)指数表示
setiosflags(ios::left)左对齐
setiosflags(ios::right)右对齐
setiosflags(ios::skipws)忽略前导空白 ?
setiosflags(ios::uppercase)十六进制数大写输出
setiosflags(ios::lowercase)十六进制数小写输出
setprecision:设置数字精度;
fixed:浮点数设置成普通的小数显示;
scientific:浮点数用科学计数法。
setprecision设置的是整个数值的有效位数,而不是小数部分的有效位数。
uppercase:对于进制前缀、十六进制a-f和浮点数科学计数法的字母e,均显示大写字母;
nouppercase:对于进制前缀、十六进制a-f和浮点数科学计数法的字母e,均显示小写字母,为默认值;
setiosflags(ios::showpoin)显示小数点
setiosflags(ios::showpos)显示符号(正负号)
setiosflags(ios::showbase)指定在数值前输出进制(0八进制 0x或0X十六进制)
位运算:按位与&:将两个运算量中的每一个位进行逻辑与操作0 0为0 0 1为0 1 1为1
eg:计算3&5
3:00000011
5:00000101
3&5:00000001
将某一位置0,其他位置不变
按位或|:将两个运算量的每一个位进行逻辑或操作
3|5:00000111
按位异或^:
计算071^521
071:00111001
052:00101010
071^051:00010011
若对应位相同则结果该位为0
若对应位不同则结果该位为1
用途:使特定位翻转(与0异或保持原值,与异或取反)
例如:要01111010低四位翻转:
01111010
(^)00001111
01110101
取反~:单目运算符,对一个二进制数按位取反
eg:025:0000000000010101
~025:1111111111101010
移位
左移运算(<<)
左移后 低位补0 高位舍弃
右移运算(>>)
右移后低位舍弃,高位:无符号数:补0,有符号数:补“符号位”
-8左移为0xf8
11111000->11110000 0xf0
10010000 -16
11110000右移
11111000
Q1:有 a、b、c、max 四个变量,求 a、b、c 中的最大值,并将结果放入 max 中。
A1:主要考查对条件表达式的理解和书写。答案为:max=a>b?(a>c?a:c):(b>c?b:c)。
Q2:求解下列各表达式的值(其中 x 的值为 100)。
(1) (a=1,b=2,c=3)
(2) 1|3<<5
(3) 'a'+3&&!0%1
(4) x%2?“odd”:” even”
A2: (1)逗号表达式的值是其最后一个表达式的值。答案为:3。
(2) <<运算符的优先级高于|运算符,所以先算 3<<5 结果为 96(二进制 1100000),然后与1 做按位与运算则结果为 97(二进制 1100001)。答案为:97。
(3) 参与本题的运算符,按优先级由高到低依次是:!运算符、算术运算符、逻辑运算符。'a'+3 时字符型首先隐式转换成整型然后相加结果为 100,!0%1 即 1%1 结果为 0,100&&0结果为 0。答案为:0。
(4) 算术表达式的优先级高于条件表达式,所以先算 x%2 结果为 0,0?”odd”:”even”结果为”even”。本题完成判断一个数是奇数还是偶数,若该数为奇数,则表达式的值为”odd”,为偶数,则表达式的值为”even”。答案为:”even”。
#include <iostream>
#include <iomanip>
Using namespace std;
void main()
{
int a = 23;
double b = 23.123456789;
cout<<a<<'\t'<<b<<endl;
cout<<setprecision(0)<<b<<endl
cout<<setiosflags(ios::fixed)<<setprecision(7)<<b<<endl;
cout<<setiosflags(ios::scientific)<<b<<endl;
cout<<setprecision(6);
cout<<setiosflags(ios::showbase);
cout<<hex<<a<<'\t'<<a<<endl;
cout<<dec;
cout<<setw(10)<<setfill('*')<<setiosflags(ios::left)<<a<<endl;
cout<<setfill(' ');
}
解答:
本题主要考查对格式化输入输出的掌握。
①本题主函数中第三行输出 a,b,’\t’为转义字符,其含义是跳过一个制表位。不设置输出宽度时,默认输出 6 位有效数字,超出部分四舍五入。所以该行输出为:23 23.1235。
② setprecision(n)设置显示精度,最少显示一位有效数字。如果不重新设置,则其保持效力,所以使用完后要还原为 6 位默认值。第四行中设置 setprecision(0)与 setprecision(1)
作用相同,结果显示一位有效数字即为:2e+001。
③ setiosflags(ios::fixed)为固定的浮点显示,其后跟 setprecision(n)表示小数点后显示精度为 n。所以第五行输出结果为:23.1234568。
④ setiosflags(ios::scientific)为指数显示,当其整数部分宽度大于设置的显示精度(默认为 6 位)时,以指数形式显示结果。否则根据设置的(或默认的)显示精度显示 n 位有效
数字。所以第六行输出结果为:23.12346。
⑤ setiosflags(ios::showbase)为指定在数值前输出进制。hex 置基数为 16,且该操作保持效力,所以使用完后应该恢复为默认值 10 进制。第九行输出结果为:0x17 0x17。
⑥setw(n) 设域宽为 n 个字符,setfill(c) 设填充字符为 c ,setiosflags(ios::left)为左对齐。第十一行输出结果为:23********。
答案为:
23 23.1235
2e+01
23.1234568
23.12346
0x17 0x17
23********
3.1顺序控制语句
常见的表达式语句:空语句、赋值语句、函数调用语句
空语句:指只有一个分号而没有表达式的语句,它不作任何操作和运算。格式为: ;
空语句被用在程序的语法上要求一条语句而逻辑上却不需要的时候。
连续输入多项数据时应该键入空白字符(空格、回车、tab)将相邻的两项数据分开
连续输出时cout不会在相邻数据项间加分隔符,为了增强现实效果可以通过控制符自定义(iomanip)
空语句和空复合语句{ }是等价的
3.2.2 switch 语句
格式:switch(<表达式>)
{
case <常量表达式 1>:<语句序列 1>
case <常量表达式 2>:<语句序列 2>
case <常量表达式 n>:<语句序列 n>
[default:<语句序列 n+1>]
}
注:switch中的<表达式>值只能是整型/字符型/枚举型表达式
switch 语句中,case 和其后的<常量表达式>间必须有空格否则会产生逻辑错误。
每个 case 分支语句结束后都要加一个 break 语句来结束 switch 语句。但在要表示一个范围,或描述一类对象时(如 A,B,C 都属于合格,D 属于不合格)有可能几条 case 分支语句后才有一个 break 语句。
do...while语句:格式: do
{
<语句>
}while(<条件表达式>);
continue:结束当前正在执行的这一次循环(for、while、do…while),接着执行下一次循环。即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环语句的判定。
4.1数组
数组名是一个标识符,代表数组在内存中的起始地址.数组中各元素在内存中连续存储
定义数组时对其中全部或部分元素指定初始值,这成为数组的初始化
数组名[常量表达式]={值1,值2,....};
当初始化的元素比数组中的元素个数少时,则按顺序对前一部分元素赋初值,其余元素自动初始化为 0 或空字符‘\0’(对字符数组);当初始化的元素超过数组元素个数时,编译器会报错。
对字符数组进行初始化可以对每个数组元素一一赋初值,也可以将一个字符串直接赋值给一个数组.但要注意数组长度除了包含字符串中个数还包含一个'\0'字符
通过 sizeof(数组名)/sizeof(数组类型)来求得实际的数组长度。
二维数组存放"先行后列"
对二维数组的初始化主要有两种形式:
第一、 数值按行用花括号分组对二维数组初始化。
第二、 所有数值按顺序在一个花括号中给出。
对以上两种形式,如果没有给出所有数组元素,则剩余元素自动初始化为 0。
若在一个花括号中对所有元素赋初值或者按行用花括号分组而组内元素部分或全部赋值,则可以缺省第一维的长度,但是[]不能省略,并且在任何情况下,二维数组第二维的长度均不可省略。
**数组定义中数组长度不能指定为除 const 变量以外的变量
为字符数组赋值可以直接从键盘键入一个字符串/用strcpy函数将一个字符串赋值到该字符数组或者用循环语句逐个为字符数组元素赋值.
不能直接将一个数组赋值给另一个数组
5.1函数
函数类型 函数名 (形参列表)
{
函数体
};
任何情况下不能在一个函数中定义另一个函数.
函数原型:编译器可以对函数调用进行检查
格式:函数类型 函数名(形参列表);
值传递:形参改变不影响实参,生成实际参数值的副本并传递给被调用函数的形式参数
引用传递:是将形参作为实参的别名,所以通过形参可以直接访问实参数据,对形参值的改变就是对实参值的改变
引用传递需在定义形式参数时在形参前加引用符"&";
地址传递:地址传递是将实参的地址传递给形参,所以对形参所指地址中的内容进行修改,也会使实参值发生改变
需将形式参数的类型定义为指针类型 *
函数调用时实参与形参按照从左到右顺序匹配,当实参全部匹配而形参还有剩余时,则剩下的形参采用默认值。在对默认值进行定义时应该从右向左定义,在一个没有默认值的参数的左边又出现有默认值的参数是错误的。默认参数应在函数名首次出现时定义。
定义内联函数时在函数定义前加关键字 inline。
内联函数适用于经常使用的小函数。对于内联函数的函数体有一些限制:
①内联函数中不能含有任何循环以及 switch 和 goto 语句;
②内联函数中不能说明数组;
③递归函数(自己调用自己的函数)不能定义为内联函数。
5.5函数重载
函数调用是根据参数的类型 个数决定具体调用哪个函数
首先进行参数完全匹配,无法完全匹配时按隐式数据类型转换的方向进行匹配,仍无法匹配就报错
函数重载解析与函数定义或声明的顺序无关
当多个函数参数个数及类型均相同,只有函数返回值类型不同时则报错
5.6数组参数
数组作为函数的参数时,传递的是数组中第0个元素的地址(指针),因此在被调用函数时对形参数组值的改变将被应用到实参数组
数组长度不是参数类型的一部分,函数不知道传递给它的数组的实际长度,当编译器对实参类型进行参数类型检查时并不检查数组的长度,因此在定义形参时可以只写数组名[]
有时在被调用函数中需要知道数组长度,可以采用下面两种方式传递数组长度信息:
① 提供一个含有数组长度的额外参数,即定义一个用于存放数组长度的形参。
② 将被调用函数的形式参数声明为数组的引用,当形式参数是一个数组类型的引用时数组长度成为形式参数类型的一部分,编译器会检查数组实参的长度与在函数形参类型中指定的长度是否匹配。
5.7变量的作用域与生存期
5.7.1局部变量与全局变量
程序的内存区域:
代码区,存放程雪的代码,即程序中各个函数的代码块
全局数据区:存放程序全局数据和静态数据
堆区:存放程序的动态数据
栈区:存放程序的局部数据(各个函数中的)
局部变量:在一个函数内部说明的
全局变量:在函数外部定义的变量
*****①在同一文件中允许外部变量和内部变量同名.内部变量的作用域内,外部变量将被屏蔽而不起作用
外部变量说明的一般形式:
extern 数据类型 外部变量[,外部变量2......];
外部变量的定义必须在所有函数之外并且只能定义一次,外部变量的说明出现在要使用该外部变量的函数内,而且可以出现多次
如果定义点之前的函数需要引用这些外部变量时,需要在函数内对被引用的外部变量进行说明。
5.7.2静态变量
静态局部变量:定义格式:static 数据类型 内部变量表;
定义但不初始化,则自动赋"0"(整型和实型)或'\0'(字符型);且每次调用他们所在的函数时不再重新赋初值,只保留上次调用结束时的值
静态全局变量
全局变量前加一个static,使该变量只在这个源文件中可用
对组成该程序的其它源文件是无效的
5.7.3生命期
静态生命期:与程序运行期相同
局部生命期:在函数内声明的变量或者是块中声明的变量具有局部生命期.
动态生命期:由程序中特定的函数调用操作符(new和delete)来创建和释放
例题:
函数在被调用之前必须先声明或定义√
strcpy()函数:是将一个字符串复制到另一块空间地址中 的函数,‘\0’是停止拷贝的终止条件,同时也会将 '\0' 也复制到目标空间。
char* strcpy(char* destination,const char* source);
1. 函数的参数:
char* destination---------目标字符串的首地址
const char* source------源地址:被复制的字符串的首地址,用const修饰,避免修改掉被拷贝的字符串
2.函数的返回值类型:
char*:返回的是目标字符串的首地址
1.源字符必须以 '\0'结束:
2.目标空间必须足够大,以确保能放源字符串
3.目标空间必须可变
strcmp
比较两个字符串
int strcmp( const char *string1, const char *string2 );
返回一个整数
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
strcmp是比较字符串中对应位置上的字符大小(ASC II码值大小),如果相同,就比较下一对,直到不同或者都遇到'\0'。
---------------以下是左值 右值------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Q1:#include <iostream>
using namespace std;
void fun1(const double& i)
{
cout<<i<<endl;
}
void fun2(double &j)
{
cout<<j<<endl;
}
void fun3(double k)
{
cout<<k<<endl;
}
void main()
{
fun1('a');
fun2('a');
fun3('a');
}
(a)97 (b)97 (c)a (d)程序出错,无法运行。
97 a a
97 97 a
A1:解答:
文字量、常量和需要类型转换的参数都可以传递给 const&参数,但不能传递给非 const 的引用参数。也就是说对非 const 引用参数不允许做类型转换。本题中对于函数 fun1 以及 fun3
在调用时参数都发生了隐形数据类型转换,而对于函数 fun2 它的参数为非 const 的引用参数,不允许作类型转换,所以对 fun2 的调用出错。因此程序出错无法运行。答案为:d。
?
Q2:***不能传递给非 const 的引用参数 What mean?
A2:字符在cpp中被看做字面常量,是一个指针无法直接改变的量,如果用double i这样的方式是创建了一个字面量的副本而没有改变原地址
const &这个新的指向字面量的指针也无法改变这块地址的值,但如果用一个非const引用意味着创建了一个能改变这个地址的值的方法
这样的行为是不被允许的,但是char a='a'; fun2(a);这个方法是可以的
eg:
#include<iostream>
int function(int &a)
{
return a;
}
int main()
{
int a = 5;
function(a);
function(1);//这一句会报错
}
报错如下:
无法将参数1从Int转换为“int &”,非常量引用的初始值必须为左值
这里的左值,可以理解为在内存中有具体的地址的变量,而不是一个短暂的临时变量。
这个左右,可以理解为,在赋值号“=” 的左边,为左值,在等号右边,为右值,但这个左右并不是绝对的。
在第一个例子里,function(int a)的参数为实参,如果传入一个1,函数会创建一个有地址的局部变量a,对a进行赋值,
让 a= 1,这里的a是一个左值,所以程序能正确运行,但在第二个例子里,
function(int &a)的参数为一个地址,function会根据这个地址去取对应的值,但是我们传入的参数是数字1,这个1显然不是左值,
因为它不是变量,没有地址,function根据地址找不到对应的变量,自然会报错。
从等号的左边到右边的答案依次是:
int a = 10; // a左值,10右值
int c = a; // c左值,a左值
std::string s1 = "Hello"; // s1左值,"Hello"右值
std::string s2 = "World"; // s2左值,"World"右值
std::string s3 = s1 + s2; // s3左值,s1 + s2右值
值得注意的是第二行和第五行,int c = a,由于两边都是变量,都有其存储空间,所以都是左值
但是第五行里,s1和s2都是左值,都有其存储空间,但是s1 + s2就不是左值了,因为相加得到的结果是一个临时变量,会被赋给s3。
左值引用与右值引用
左值引用,顾名思义就是对左值变量的引用,右值引用同理
int &a = 10; // 报错,因为无法创建一个左值的引用指向一个右值
int& GetValue(int& n)
{
return 10; // 报错,返回的不是左值引用
}
int& GetValue(int& n)
{
static int a = 10;
return a; // 正确,a是左值,返回的是左值引用
}
注意,通过关键字const,我们可以实现如下的功能
const int &a = 10; //正确
这一行代码实际上相当于,这也就是const特殊的地方:
int temp = 10;
const int &a = temp;
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Q2:例题 12:写一个函数,它以一个名字作为命令行参数,打印输出“hello,name”(其中 name
为输入命令行参数)。
A2:解答:
本程序主要考查对命令行参数的理解及使用。
说明:
void main(int argc,char* argv[]){…}
即是说,除按通常的那种无参方式来使用 main 函数外,如果需要的话,main 函数还可
带有两个上述格式以及数据类型的形式参数。
main 带有形式参数时,其对应实参值由用户在执行该程序时指定,而后通过操作系统
将它们传递给 main 函数。main 函数所含两个参数的含义如下:
argc-----第一参数,记录命令行参数的个数(其值为实际命令行参数的个数加 1);
argv-----第二参数,为字符串数组,存放执行程序名以及各实际命令行参数。各数组元素
的含义为:
argv[0]:本执行程序的文件名
argv[1]:第 1 个实际命令行参数(如果有);
…
argv[argc-1]:第 argc-1 个实际命令行参数。
在 vc6.0 开发环境下设置命令行参数方法如下:
project→settings…→debug→”program arguments:”中,输入以空格分隔的各参数。
在命令提示符环境中,可通过如下形式运行程序:
程序文件名 参数 1 …
参数之间用空格隔开。
参考程序为:
#include <iostream>
using namespace std;
void main(int argc,char* argv[])
{
cout<<"hello,"<<argv[1]<<endl;
}
------------------------------------------------------------------------------------------------------------
例题 15:打印某一年的年历。
解答:
关于本题的编程细节请参见程序中的注释。
参考程序如下:
#include <iostream>
#include <iomanip>
using namespace std;
int FirstDayOfYear(int y);
int DaysOfMonth(int m);
void PrintMonth(int m);
void PrintHead(int m);
bool IsLeapYear(int y);
int weekDay;
int year;
void main()
{
cerr<<"请输入您想要打印的年份:\n";
cin>>year;
if(year<1900)
{
cerr<<"年份不能小于 1900。\n";
return;
}
weekDay=FirstDayOfYear(year);//一年的第一天星期几
//打印年标题
cout<<"\n\n\n\n\n "<<year<<" 年日历\n";
cout<<"\n ===================================================";
//打印每个月
for(int i=1;i<=12;i++)
PrintMonth(i);
cout<<endl;
}
//某个月打印函数
void PrintMonth(int m)
{
PrintHead(m); //打印月标题
int days=DaysOfMonth(m); //该月的天数
for(int i=1;i<=days;i++)
{
cout<<setw(6)<<i;
weekDay=(weekDay+1)%7;
if(weekDay==0) //打印下一天时判断是否换行
{
cout<<endl;
cout<<" ";//行首空格
}
}
}
//打印月标题
void PrintHead(int m)
{
cout<<"\n\n"<<setw(6)<<m<<"月 日 一 二 三 四 五 六
\n";
cout<<" ";
for(int i=0;i<weekDay;i++)
cout<<" ";
}
//判断某月天数的函数
int DaysOfMonth(int m)
{
switch(m){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:return 31;
case 4:
case 6:
case 9:
case 11:return 30;
case 2:if(IsLeapYear(year))
return 29;
else
return 28;
}
return 0;
}
//判断是否为闰年
bool IsLeapYear(int y)
{
return((y%4==0&&y%100!=0)||year%400==0);
}
//判断某年的第一天
//因为每年都是 52 个整星期多一天,因此 n 年多出的天数还是 n 天,
//再加上 1900 年到 year 年间因闰年多出来的天数,
//再加上 1900 年元旦的星期序号 1 与 7 取模便得出是第几天。由于 2000 年正好是闰年,所
//以可以计算到 2099 年。
int FirstDayOfYear(int y)
{
int n=y-1900;
n=n+(n-1)/4+1;
n=n%7;
return n;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------
6.1指针
指针变量用于存放一个对象在内存中的地址
指针变量的定义 格式:
存储类型 数据类型 *指针变量名;
任何一个指针变量本身的数据类型都是unsigned long int
这里的*表示定义的是一个指针变量
&取地址运算符
*间接引用运算符
****指针定义时和引用时含义不一样!!
引用指针时的*p与定义指针时的含义不同,在指针变量的定义中 如int *p;是指p是一个指向整型的指针,在引用时*p是指p所指向的变量.
6.1.3指针运算
NO1:指针的赋值运算---当向指针变量赋值时,必须是地址常量或变量,不能是普通整数.
常见的有以下几种形式:
把一个变量的地址赋予一个指向相同数据类型的指针;
把一个指针的值赋予相同数据类型的另外一个指针;
把数组的地址赋予指向相同数据类型的指针.
NO2指针的算术运算
指针与整数的加减运算:指指针从当前指向位置向前或向后移动几个数据单元
由于指针可以指向不同数据类型,即数据长度不同的数据,所以这种运算的结果值取决于指针指向的数据类型
两个指针相减:地址之间相隔的数据元素个数
NO3指针的关系运算
实现对两个指针所指变量地址值的比较
6.1.4const指针
指向常量的指针 格式: const 数据类型 *指针变量名;
说明:可以改变指针本身的值,但不能改变指向数据的值
指针常量: 格式: 数据类型 *const 指针变量名=初始地址值;
说明:可以改变指向的数据的值,但是指针本身的值不可改变
指向常量的指针常量 格式: const 数据类型 * const 指针变量名=初始地址值;
说明:指针本身的值不可改变,它所指向的数据的值也不能通过指针改变。
6.2指针与数组
数组元素的访问:
①地址法:int a[5]; 对第i+1个元素访问可以用a[i] *(a+i)
对于二维数组如定义 int b[2][3]; 则b[0] b[1] 分别代表第一行和第二行的首地址
所以要访问数组b[i][j]可以用以下几种形式: *(*(b+i)+j) *(b[i]+j) *(b+i)[j] *(b+3*i+j)
②指针法:通过指针访问某一数组元素
如定义一个指向数组元素的指针,int a[5], *p=a; 则对数组的第i+1个元素的访问可以用*(p+i)它等价于a[i]
对于二维数组如定义int b[2][3],*q=b[0];则访问数组元素b[i][j]可以用一下几种形式:
*(*(q+i)+j) *(q[i]+j) *(q+i)[j] *(q+3*i+j) q[i][j]
数组指针与指针数组
①数组指针
格式:数据类型 (*指针名)[常量表达式];
说明:是一个指向一维数组指针的指针变量
②指针数组
格式:数据类型 *指针数组名[常量表达式];
说明:数组元素为指针的数组,即数组中每个元素为指向既定的数据类型的指针
6.3指针与函数
6.3.1指针作为函数参数
若指针作为某函数的参数,对该函数的调用即为传地址调用。
6.3.2指针型函数
当一个函数的返回值是指针类型时,这个函数就是指针型函数。它的作用是当需要返回大量数据时可以通过指针型函数完成,当然这需要有效组织数据所占的内存空间。这种情况多用于返回数组、字符串等。
定义指针函数的函数头格式是:
数据类型 *函数名 (参数表)
6.3.3函数指针
函数指针就是指向函数的指针。
定义格式:
数据类型 (*函数指针名)(参数表);
函数指针变量在使用前应该先给他赋值 格式:函数指针名=函数名;
函数指针指向某函数后,可以用以下形式调用参数: (*指针变量) (实参表列)
函数指针数组 格式: 数据类型 (*函数指针名[常量表达式]) (参数表);
说明:函数指针数组中的每个元素是一个指向既定类型函数的指针.每个元素所指向的函数具有相同的数据类型和相同的参数类型和参数个数
6.4指针与字符串
可以定义一个字符指针,通过指针的访问来访问所需的字符
常用的字符串处理函数:(包含头文件cstring)
①strcat()
函数原型:char *strcat(char* s1,char* s2)
说明:字符串连接函数,将字符串s2连接到s1后面,并返回s1的地址值
②strcmp()
函数原型:int strcmp(const char* s1,const char* s2,[int n]
说明:字符串比较函数,比较两个字符串s1和s2的大小(如果有参数n,比较前n个字符的大小)当字符串大于-等于-小于字符串s2,
分别返回正数-0-负数
1. strcmp ( ) 函数比较的不是字符串的长度,而是比较字符串中对应位置上的字符的大小(即比较的是ASCII码值,而且还要注意区分大小写),
如果相同,就比较下一对字符,直到这一对的字符不同或者都遇到 \0
2. 字符串大小的比较是以ASCII码表上的顺序来决定,此顺序亦为字符的值
③strcpy()
函数原型:char *strcpy(char *s1,const char* s2)
说明:将s2所指向的字符串复制到s1所指向的字符数组中,然后返回s1的地址值
④strlen()
函数原型:int strlen(const char *s)
函数说明:返回字符串s的长度
6.5动态存储分配
动态内存分配的存储空间在堆中,堆也称为自由存储单元.new运算符与delete运算符一起使用,就可以直接进行动态内存的申请和释放(也称为创建和删除)
6.5.1 new运算符:用于申请所需的内存单元,返回指定类型的一个指针
用new动态分配某种类型的变量
格式:指针=new 数据类型;
说明:指针应预先声明,指针指向的数据类型与 new 后的数据类型相同。若申请成功,则返回分配单元的首地址给指针;否则(比如没有足够的内存空间),则返回 0(一个空指针)。
用 new 动态分配数组
格式:指针=new 数据类型[常量表达式];
说明:常量表达式给出数组元素的个数,指针指向分配的内存首地址,指针的类型与new 后的数据类型相同。
6.5.2delete运算符,用于师范new申请到的内存空间
格式一:delete 指针;
作用:释放非数组内存单元
格式二:delete[常量] 指针;
作用:释放数组内存单元.
其中,指针是指向需要释放的内存单元的指针的名字,并且delete只是删除动态内存单元,并不会将指针本身删除
6.6引用的概念及应用
引用是个变量的别名,当建立引用时,程序用另一个变量或对象(目标)的名字初始化它。引用通常用来做函数的参数或函数的返回值。
声明引用的格式为: 类型 &引用名=变量名;
说明:①引用在声明时必须初始化,否则会产生编译错误
②引用一旦初始化,它就维系在一定的目标上,再也不分开。任何对该引用的赋值,都是对引用所维系的目标赋值,而不是将引用维系到另一个目标上。
③对 void 进行引用是不允许的。
④不能建立引用的数组。
⑤引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。
⑥不可以有空引用。
-----------------例题--------------------------------------------------------------------------------------
指针不能被赋予非地址值,指针不能被初始化或赋值为其他类型对象的地址值。
--------------------------------------------------------------------------------
8.1类和对象
8.1.1类的定义
类定义格式:
class 类名
{
private:
私有数据成员和成员函数
protected:
保护数据成员和成员函数
public:
公有数据成员和成员函数
};
类成员的访问控制
类成员有三种不访问权限:私有(private)、保护(protected)、公有(public)。
私有成员只能被本类的成员函数访问及其友元访问。当声明中未指定访问控制时,系统默认该成员为私有成员。
保护成员一般情况下与私有成员的含义相同,在类的继承和派生中与私有成员有不同的含义,保护成员可被本类或派生类的成员函数访问,但不能被外部函数访问。
公有成员可以被程序中的任何函数访问,它提供了外部程序与类的接口功能。
类的数据成员与成员函数
*!*类定义中声明数据成员的数据类型和名称,不能在类内说明数据成员的同时为其赋初值,只有在类的对象定义以后才能给数据成员赋初值。
对于成员函数可以在类内定义,也可以在类内给出函数原型,然后在类外对成员函数进行定义。成员函数在类内说明原型,在类外给出定义时其定义格式如下:
返回类型 类名::函数名(参数表)
{
//函数体
}
在所定义的函数名前必须加上类名,以表示该函数属于哪个类。类名与函数名之间必须加上作用域运算符∷。
注意:如果在类的内部定义成员函数,该成员函数即被声明为内联函数。也可以在类中声明,在类外将该成员函数定义为内联函数。只要在定义前加关键字 inline,以显示定义该成员函数为内联函数。这种函数一般是小的、频繁使用的函数。
内联函数的声明有显式声明和隐式声明两种形式。
隐式声明:直接将成员函数定义在类内部
显示声明:将内联函数定义在类外,其声明的形式与在类外定义成员函数的形式类似,但为了使成员函数起到内联函数的作用,在函数声明前要加关键字 inline,以显式地声明这是一个内联函数
8.1.2 对象的定义和使用
对象的定义:定义一种新的类型数据类型,只有实例化后才会分配存储空间
格式:类名对象名 (参数列表);
说明:可以同时定义多个对象,之间用逗号隔开
对象成员的引用:
格式:
对象名.数据成员
对象名.成员函数(实参表);
说明:①在引用成员时要注意访问权限的控制问题
②对于指向对象的指针在引用其成员时不能使用"."运算符.其格式为:
对象名->数据成员;
对象名->成员函数(实参表);
8.2构造函数和析构函数
8.2.1构造函数:是一种特殊的成员函数,作用是为累的对象分配内存空间,进行初始化
说明:
①构造函数的名字必须与类的名字相同
②构造函数没有返回值,不能定义返回类型,包括void在内
③对象定义时,编译系统会自动地调用构造函数完成对象内存空间的分配和初始化工作。
④构造函数是类的成员函数,具有一般成员函数的所有性质,可访问类的所有成员,可以是内联函数,可带有参数表,可带有默认的形参值,还可重载。
⑤如果没有定义构造函数,编译系统就自动生成一个缺省的构造函数,这个缺省的构造函数不带任何参数,仅给对象开辟存储空间,不完成对数据成员赋初值。此时数据成员的值
是随机的。系统自动生成的构造函数的形式为:
类名∷类名()
{
}
8.2.2析构函数:其作用是释放分配给对象的内存空间,并做一些善后工作。
说明:①析构函数的名字必须是 ~类名
②析构函数没有参数,返回值,不能重载
③当对象撤销时,系统会自动调用析构函数完成内存空间的释放和善后工作。
④如果没有定义析构函数,系统会自动生成一个缺省的空析构函数。完成善后工作,其形式为:
类名::~类名()
{
}
对于构造函数和析构函数常见用法是在构造函数中用 new 动态申请空间,在析构函数中用 delete 释放内存空间。
8.2.3拷贝构造函数:其作用是用一个已经存在的对象初始化本类的新对象。每个类都有一个拷贝构造函数,它可以是根据用户的需要自定义,也可以由系统自动生成。拷贝构造函数名与类名相同,但参数是本类对象的引用。拷贝构造函数没有返回值。
定义拷贝构造函数的格式为:
类名(类名&对象名)
{
//函数体
}
其中对象名是用来初始化另一个对象的对象的引用
构造函数只在对象被创建时自动调用,而拷贝构造函数在下列三种情况下会被自动调用:
①用一个对象去初始化本类的另一个对象时。
②函数的形参是类的对象,在进行形参和实参的结合时。
③函数的返回值是类的对象,函数执行完返回时。
8.2.4对象成员
定义:当用一个类的对象作为另一个类的成员,该成员称为对象成员,声明对象成员的一般格式:
class 类名
{
类名1 对象成员名1;//需要此类在前面已经定义或声明
...
};
对象成员的初始化:在类中有对象成员时,创建本类的对象则本类的构造函数要调用其对象成员所在类的构造函数,并采用成员初始化列表对对象成员进行初始化。这种类的构造函数的定义格式为:
类名∷类名(参数总表):对象成员 1(形参表),...,对象成员n(形参表)
{
//构造函数体
};
说明:对象成员的构造函数的调用顺序由对象成员在类中的声明顺序决定,与成员初始化列表中的顺序无关。析构函数的调用顺序正好与构造函数的调用顺序相反。
8.3 对象数组与对象指针
8.3.1对象数组:指数组中每个元素都是一个类的对象.这些对象属于同一个类
定义一维对象数组的一般格式:
类名 数组名[常量表达式];
对象数组的引用
由于对象数组的元素是对象,只能访问其共有成员.引用格式:
数组名[下标].公有成员
8.3.2对象指针:对象在内存中的首地址
指向类类型的指针变量用于存放对象指针,其定义格式为:
<类名> * <指针变量名>;
说明:可以再定义的同时对该指针变量进行初始化 即用"&对象名"的形式取出对象首地址赋给该变量,也可以在使用该指针变量时再对它赋值
8.4静态成员:指类中用关键字static说明的成员,仍然服从访问控制
8.4.1静态数据成员:属于类不属于某个对象,实现同类对象之间的数据共享
在类中声明静态数据成员时必须加static说明
对静态数据成员初始化只能在类外进行,一般在类声明与main()之间的位置
格式为:
数据类型 类名::静态数据成员名=值;
对静态数据成员的引用可以有两种形式:
类名:静态数据成员
对象名.静态数据成员
8.4.2静态成员函数:类中用关键字 static 说明的那些成员函数。
可以用静态成员函数在未建立任何对象之前去处理静态数据成员。静态成员函数只能直接引用该类的静态数据成员和静态成员函数,不能直接引用非静态数据成员。
调用静态的两种形式:
类名∷静态函数名();
或 对象名.静态函数名();
8.5友元:是单向的,不能传递
8.5.1友元函数
一个普通函数作为某个类的友元时即为友元函数。在该函数中可以访问其由 friend 声明语句所在的类的对象的私有成员和公有成员。在类中作如下声明,则说明该函数不是本类的成员函数,而是友元函数。
friend 函数类型 友元函数名(参数表);
友元函数的定义可以在类内也可以在类外,在类外定义时不需要加类名和普通函数定义没有区别。通常友元函数的定义在类外进行。
友元函数不是类的成员,因而不能直接引用对象成员的名字,也不能通过 this 指针引用对象的成员,必须通过作为入口参数传递进来的对象名或对象指针来引用该对象的成员。为此,友元函数一般都带有一个该类的入口参数。
8.5.3友元类
当一个类作为另一个类的友元时即为友类。若类 A 是类 B 的友类,则类 A 中的所有成员函数都是类 B 的友元成员函数,所以可以通过对象名访问 B 的私有成员和公有成员。当类 A 为类 B 的友类时,在类 B 中的声明格式为:
friend class <友元类名>; 或 friend <友元类名>;
8.5.2 友元成员函数
某个类的成员函数作为另一个类的友元即为友元成员函数。通过友元成员函数,可以访问由 friend 声明语句所在的类的对象的私有成员和公有成员。当一个类 A 的成员函数作为另一个类 B 的友元函数时,在类 B 中的声明格式为:
friend 函数类型 成员函数所在类类名::函数名(参数表);
----------------------------------------------------------------------
Q1例题 3.下列说法正确的是( )。
(a) 可以定义修改对象数据成员的 const 成员函数。
(b) 不允许任何成员函数调用 const 对象,除非该成员函数也声明为 const。
(c) const 对象可以调用非 const 成员函数。
(d) const 成员函数可以调用本类的非 const 成员函数。
A1解答:
c++编译器不允许任何成员函数调用 const 对象,除非该成员函数本身也声明为 const。声明
const 的成员函数不能修改对象,因为编译器不允许其修改对象。对 const 对象调用非 const
成员函数是个语法错误。定义调用同一类实例的非 const 成员函数的 const 成员函数是个语
法错误。答案为:b
Q2:例题 4.运行下列程序后,” constructing A!” 和” destructing A!”分别输出几次( )。
#include<iostream.h>
class A
{
int x;
public:
A()
{cout<<" constructing A!"<<endl;}
~A()
{cout<<" "<<endl;}
};
void main()
{
A a[2];
A *p=new A;
delete p;
}
(a)2 次,2 次 (b)3 次,3 次 (c)1 次,3 次 (d)3 次,1 次
A2:解答:
本题主要考查在什么情况下系统会调用构造函数与析构函数。在主函数中定义了一个对象数组,其中有两个元素,该数组中的每个元素都是一个类的对象,所以这里会调用 2 次构造函
数;new A 时创建一个 A 类的对象,所以也会调用构造函数,因此一共调用 3 次构造函数。delete p;会撤消 new 运算分配的空间,它会调用 1 次析构函数。主函数结束时要释放数组所
占空间,会调用 2 次析构函数,因此析构函数也调用了 3 次。答案为:b
Q3:例题 7.运行下列程序结果为________________________。
#include<iostream.h>
const double PI=3.14159;
class circle
{
double r;
public:
static int num;
circle(double);
circle(circle &);
double getr();
};
circle::circle(double i)
{
r=i;
}
circle::circle(circle &c)
{
num++;
cout<<"第"<<num<<"次调用拷贝构造函数!"<<endl;
r=c.r*num;
}
double circle::getr()
{
return r;
}
double getradius(circle c3)
{
return c3.getr();
}
circle fun1()
{
circle c4(5);return c4;
}
int circle::num=0;
void main()
{
circle c1(1);
cout<<"c1:"<<c1.getr()<<endl;
circle c2(c1);
cout<<"c2:"<<c2.getr()<<endl;
cout<<"c3:"<<getradius(c1)<<endl;
circle c4(1);
c4=fun1();
cout<<"c4:"<<c4.getr()<<endl;
}
A3:本题主要考查在什么情况下会调用拷贝构造函数。构造函数只在对象被创建时自动调用,而
拷贝构造函数在下列三种情况下会被自动调用:
***********①用一个对象去初始化本类的另一个对象时。
***********②函数的形参是类的对象,在进行形参和实参的结合时。
***********③函数的返回值是类的对象,函数执行完返回时。
本题答案为:
c1:1
第 1 次调用拷贝构造函数!
c2:1
第 2 次调用拷贝构造函数!
c3:2
第 3 次调用拷贝构造函数!
c4:15
常量是不能被赋值的,一旦初始化后,其值就永不改变,引用变量也是不可重新指派的,初始化后,其值就固定不变了。
Q4:例题 9.运行下列程序结果为__________________。
#include<iostream.h>
class Obj{
static int i;
public:
Obj(){i++;}
~Obj(){i--;}
static int getVal(){return i;}
};
int Obj::i=0;
void f (){Obj ob2;cout<<ob2.getVal();}
int main(){
Obj ob1;
f();
Obj*ob3=new Obj;cout<<ob3->getVal();
delete ob3;cout<<Obj::getVal();
return 0;
}
A4:解答:
本题主要考查对静态数据成员的理解。在主函数中创建对象 ob1 则调用该类的构造函数,使得静态数据成员加 1,为 1;接着调用函数 f(),在函数中创建对象 ob2,这时再次调用构造
函数,使得静态成员的值为 2,ob2.getVal()返回静态数据成员 i 的值,即输出 2。函数 f()结束,则 ob2 的生存期结束,自动调用其析构函数使静态数据成员 i 的值变为 1。接着在主
函数中用 new 运算符动态分配存储空间,又一次调用构造函数使 i 加 1,所以再次输出时 i的值为 2。最后用 delete 释放 ob3 所指的对象空间,则会调用析构函数使 i 的值减 1,因此输出 i 的值为 1。本题答案为:221
**我的疑问:static 并不是初始化以后就不可修改了
Q5:例题 10.若类 A 是类 B 的友元,类 B 是类 C 的友元,则下列说法正确的是( )。
(a)类 B 是类 C 的友元 (b)类 A 是类 C 的友元
(c)类 A,B,C 互为友元 (d)以上说法都不对
A5:解答:
本题考查对友元关系的理解。友元关系是单向的,也是不能传递的。答案为:a
----------------------------------------------------------------------------------------------------------------------------------
9.1继承性与派生类
继承:从现有类的基础上建立新类,集成现有的属性和方法,拥有特有的属性和方法,继承的过程称为派生,
新建的类称为派生类,原有的类称为基类,继承可分为单继承和多重继承
若派生类只有一个基类则称为单继承,若派生类有多个基类则称为多重继承
9.2派生类的声明和访问权限
9.2.1派生类的声明
单继承中派生类的定义格式:
class <派生类名>: <派生方式><基类名>
{
派生类新定义的成员声明;
};
说明:
①派生方式关键字为private/public/protected,分别表示私有 公有 保护继承,缺省的继承方式是私有继承
继承方式规定了派生类成员和类外对象访问基类成员的权限
②派生类新定义的成员是指继承过程中新增加的数据成员和成员函数,通过在派生类中新增加成员实现功能的扩充
9.2.2派生类访问权限
公有继承(public):
继承后公有私有保护成员在派生类中访问权限保持不变
在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
在基类和派生类定义以外只能通过派生类的对象访问基类的公有成员,无法通过派生类对象直接访问基类的私有成员和保护成员。
私有继承(private):
①继承后基类的所有成员在派生类中均为私有成员
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
保护继承(protected):
①继承后基类的公有成员和保护成员在派生类中均为保护成员,基类的私有成员在派生类中仍为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
9.3派生类构造函数和析构函数的定义及使用
在派生过程中,构造函数和析构函数不被继承。在创建一个派生类对象时,
分别调用基类和派生类的构造函数,完成各自成员的初始化工作。当撤销一个派生类对象时,
分别调用基类和派生类的析构函数完成善后处理工作。
在 C++中对构造函数与析构函数的调用顺序有如下规定:
①对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。
②对于析构函数,先执行派生类的,再执行对象成员的,最后执行基类的。
派生类构造函数定义格式为:
<派生类名>::<派生类名>(参数总表):基类名(参数表),对象成员名 1(参数表 1),…,对象成
员名 n(参数表 n)
{
//派生类新增成员的初始化语句
}
说明:①派生类的构造函数名与派生类名相同
②参数总表列出初始化基类成员数据、新增对象成员数据和派生类新增数据成员所需要的全部参数。
③冒号后列出需要使用参数进行初始化的基类的名字和对象成员的名字及各自的参数表,之间用逗号分开。对于使用缺省构造函数的基类或对象成员,可以不给出类名或对象名以及参数表。
④如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,保证在基类进行初始化时能获得所需的数据。
⑤如果派生类的基类也是派生类,则每个派生类只需负责其直接基类的构造,不负责其间接基类的构造。
9.4多重继承的声明,构造函数,析构函数的定义和使用
9.4.1多重继承的声明
格式:
class <派生类名>:<派生方式1><基类名1>,...,<派生方式n><基类名n>
{
派生类成员声明;
};
说明:这里的派生方式以及访问权限定义与单继承规则相同
9.4.2 多重继承的构造函数与析构函数
①对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。多个基类构造函数的执行次序严格按照声明时从左到右的顺序来执行的,与定义派生类构造函数
时指定的初始化表中的次序无关。多个对象成员所在类的构造函数的执行次序按照对象成员定义的顺序来执行。
②析构函数的调用顺序正好与构造函数的调用顺序相反。
定义多重继承构造函数格式:
<派生类名>::<派生类名>(参数总表):基类名1(参数表1),...基类名n(参数表n),对象成员名1(对象成员参数表1),...,对象成员名m(对象成员参数表m)
{
//派生类新增成员的初始化语句
}
说明:单继承中构造函数定义的说明在多重继承构造函数中均适用
9.5 虚基类的作用、定义和使用
9.5.1多重继承中的二义性问题
问题产生:①当派生类继承的多个基类中存在同名成员时,派生类中就会出现来自不同基类的同名成员,就出现了标识符不唯一或二义性的情况,这在程序中是不允许的
②当一个类从多个基类派生而来,这多个基类又有共同的基类,则在派生类中访问这个共同基类中的成员时会产生二义性
解决方法:
对于第一种情况:
①使用作用域运算符"::"
②使用同名覆盖的原则
③使用虚函数
对于第二种:使用虚基类
9.5.2虚基类
eg:对于非虚基类的情况,derived 有两个基类 base1 和 base2,这两个基类又有共同基类 base,derived 类中就有基类 base 的两个不同的拷贝。在 derived 要访问 base
类中的成员时,就会产生二义性问题。虚基类就是为了解决这个问题而引入的。如上图所示,对于虚基类的情况,derived 中的公共基类 base 就只有一个拷贝而不会出现二义性问题。
9.5.3虚基类的定义
虚基类的声明时在派生类的生命过程中进行的,格式为:
class<派生类名>:virtual <派生方式><基类名>
说明:①虚基类关键字的作用范围和派生方式与一般派生类的一样,只对紧跟其后的基类起作用。
②声明了虚基类以后,虚基类的成员在进一步派生过程中和派生类一起维护同一个内存拷贝。
9.5.4 虚基类的构造函数和初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的执行顺序不同:
①虚基类的构造函数在非虚基类的构造函数之前执行。
②若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的先后次序执行。
③ 若虚基类由非虚基类派生而来,则仍然先执行基类的构造函数,再执行派生类的构造函数。
---------------------------------------------------------------------------------------------------------------------
在多重继承中,多个基类的构造函数的调用顺序由在定义派生类时基类的声明顺序决定。
基类的构造函数和析构函数不能被派生类继承。
**填空:cpp常量表达式的关键字是什么 constexpr
常量(const)和常量表达式是两个东西
第 10 章 多态性与虚函数
10.1多态性的概念
面向对象的概念中,多态性是指不同对象接收到相同消息时,根据对象类的不同产生不同的动作
由静态联编支持的多态性称为编译时的多态性或静态多态性,也就是说,确定同名操作的具体操作对象的过程是在编译过程中完成的。C++用函数重载和运算符重载来实现编译时的多态性。
由动态联编支持的多态性称为运行时的多态性活动太多态性,也就是说,确定同名操作的具体操作对象的过程是在运行过程中完成的。C++用继承和虚函数来实现运行时的多态性。
10.2 函数和运算符的重载
10.2.1 函数重载
函数重载表现为两种:1.参数个数或类型有所差别的重载,2.函数的参数完全相同但属于不同的类
10.2.2运算符重载
C++预定义的运算符只是对基本数据类型进行操作,而对于自定义的数据类型比如类,却没有类似的操作。为了实现对自定义类型的操作,就必须自己编写程序来说明某个运算符作用在这些数据类型上时,应该完成怎样的操作,这就要引入运算符重载的概念。
运算符重载形式有两种:1.重载为类的成员函数 2.重载为类的友元函数
将运算符重载为它将要操作的类的成员函数,称为成员运算符函数
实际使用时,总是通过该类的某个对象访问重载的运算符
成员运算符函数的定义:
在类内声明的一般形式:
<返回类型> operator<运算符>(参数表);
在类外声明的一般形式:
<返回类型><类名::> operator<运算符>(参数表)
{
函数体