-
Notifications
You must be signed in to change notification settings - Fork 0
/
地信 cpp review ver2
794 lines (623 loc) · 31.3 KB
/
地信 cpp review ver2
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
十进制数转换为R进制数:除r取余,自下而上法
十进制小数转换为R进制小数:乘r取整,自上而下
十进制转换二进制:除二取余算整数,乘2取整算小数
位(bit)度量数据的最小单位
bit B K M G T
1B=8bit,剩下均为1024
浮点数构成:阶码和尾数
阶码是指数,尾数是纯小数
正数的原码反码补码都相同
ASCII码:A-Z 65-90
a-z 97-122
0-9 48-57
计算机组成:运算器、存储器、控制器、输入和输出设备
指令和数据用二进制码表示,同等地位存放于存储器内,并可按地址寻访
指令在存储器内按顺序存放,通常顺序执行,按下可改变执行顺序
特性:有穷性,确定性(每一个步骤都必须有确切的定义,不能有歧义),
输入(一个算法内有0或多个输入),输出(一个算法内有1或多个输出),有效性()每一个步骤应当能有效的执行)
程序设计的基本步骤
需求分析-算法设计-画流程图-编写代码-上机调试
长整型整数以L或l结尾
无符号整数以u或U结尾,以UL或LU结尾可表示无符号长整型整数
单引号引起来的单个字符是字符型常量,'a'占一个字节,存放'a' 值为0x61
双引号引起来的若干个字符是字符串常量“a”占两个字节,存放'a'和'\0',值为0x6100
标识符常量:用const给文字常量起的名字(标识符),也称为常变量
常变量只能在声明时进行初始化,初始化后不允许再被赋值
cin自动跳过输入的空白字符(制表符 回车 空格)
case之后可以接常量数值,字符,枚举类型常量
case之后不能接变量或者
case之后不能接实型数或带有实数的常量表达式
错误的例子:先定义,后整体赋值
//错误1
int a[6];
a={0,1,2,3,4,5};
对数组的访问指的是对数组元素的访问
不能直接将数组名作为访问对象;
在放弃初始化方法后,必须对每一个元素逐个赋值。
数组不能整体访问,只能访问数组元素
字符数组可以当作整体通过操作数组名来处理
注意:如果结尾没有加'\0'就不能这样操作。而且其他类型数组不能访问数组名。
只有字符数组允许整体输入和输出
以数组名输出时,数组如果没有结束符,会导致输出错误
以数组名输入时,系统会自动添加结束符,以字符串的方式存储数组
char name[5];
cin>>name;
运行时输入数据:hust(回车)
存储为 h u s t \0
以数组名输入时,系统会自动添加结束符,以字符串的方式存储数组
用"cin>>数组名"的形式输入时,遇到空格,Tab键或回车键就表示输入结束;
因此,输入单个字符串时,其中不能有空格
cin.getline(字符数组名, 接受字符的最大个数n, 指定结束符)
将输入的字符放入字符数组中
默认输入回车表示输入结束
放入数组中的字符数量取决于接受字符的最大个数n
和指定结束符
系统提供的字符串处理函数定义在头文件<cstring>中
& :取地址运算符
*:间接引用运算符
*p : 访问指针p所指向的内存的数据
允许声明void 类型的指针。该指针可以被赋予任何类型对象的地址。
& 取地址运算符 作用于内存中一个可寻址的数据(如变量,对象和数组元素等),操作结果是获得该数据的地址。
* 间接运算符 作用于一个指针变量,访问该指针所指向的内存变量
释放了point所指目标的内存空间,指针point本身并没有撤销。
point 指针变成悬空指针,建议这时将point置空(NULL)。
1.用初始化式(initializer)来显式初始化
int *pi=new int(0);
2.当pi生命周期结束时,必须释放pi所指向的目标:
delete pi;
注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,该指针所占内存空间并未释放
pi=NULL;
new和delete中的方括号必须配对使用。
如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。
delete [ ]的方括号中不需要填数组元素数。
创建时不能对数组元素进行初始化
请注意“下标表达式“不要求常量表达式,即它的值不必在编译时确定,可以在运行时确定
选择排序
选择排序的基本思想:每一次从待排序的数组中选出值最小的元素,放在待排序子序列的第一个位置;
在剩下的元素中继续上述操作,直到全部元素排序完成。
找出最小值的下标。
min=0;
for(i=1;i<=SIZE;i++)
if(arr[min]>arr[i])
min=i;
将最小元素与第一个元素交换
tmp=arr[0]; arr[0]=arr[min]; arr[min]=tmp;
8个元素的数组将上述过程循环8-1次即可,且每次将第一个元素排除在外。
函数声明、函数定义及函数调用
函数声明
意义
在调用函数前先声明其返回值类型、函数名和参数
函数原型有助于编译器对函数参数类型的匹配检查
一个函数声明需要给三个关键部分:
函数的名字
函数返回值的类型
调用这个函数时必须提供的参数个数和参数类型。
函数声明格式
数据类型 函数名(形参列表);
形参列表:格式类似变量的定义,但每个形参必须给出独
立的类型说明。
函数返回值
函数的值只能通过return语句返回主调函数。
return 语句的一般形式
return 表达式; 或 return (表达式);
功能:计算表达式的值,并返回给主调函数。
在函数中允许有多个return语句(通常在条件语句中),但每次调用只能有一个return 语句被执行;
当return语句执行时,结束函数的执行,返回到主调函数,其他return语句不会被执行。
函数的返回值应当属于某一个确定的类型
在定义函数时指定函数返回值的类型
函数值的类型和函数定义中函数的类型应保持一致
如果两者不一致以函数类型为准
将返回值自动进行类型转换
不返回函数值的函数,可以明确定义为空类型,类型说明符为 “void
函数定义
指函数功能的确立
指定函数名、函数类型、形参及类型、函数体等
是完整独立的单位
函数声明
是对函数名、返回值类型、形参类型的说明
不包括函数体
是一条语句,以分号结束,只起一个声明作用
函数调用
函数名(表达式1, 表达式2, ……)
实际参数 函数调用时提供的表达式
有返回值时
放到一个数值表达式中
c = max(a,b);
cout 输出流中调用函数
cout<<max(a,b);
作为另一个函数调用的参数
c = max(max(a, b),c);
无返回值时
函数调用表达式
display(a,b);
函数设计的基本原则
入口参数有效性检查
敏感操作前的检查
调用成功与否的检查
1.函数规模要小
2.函数功能要单一
3.函数接口定义要清楚
变量按作用域范围分为两种,局部变量和全局变量局部变量也称为内部变量。局部变量是在函数内
或者块内{}定义说明的。其作用域仅限于函数内或者块内,离开该函数或块后再使用这种变量是非法的
全局变量
全局变量也称为外部变量,它是在函数体外定义的变量。全局变量属于整个源程序文件。其作用域是整个源程序。
例如:
int a, b; //在函数体外定义,全局变量
void fun1() //fun1函数定义
{……
}
double x, y; //全局变量
int fun2() // fun2函数定义
{……
}
int main() /*主函数*/
{……
}
变量的生存期与变量的存储方式有关。从变量值存在的时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。
静态存储方式:是指在程序运行期间分配固定的存储空间的方式。
动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。
用户存储空间可以分为三个部分:
1) 代码区;
2) 静态存储区;
3) 动态存储区
auto变量
函数中的局部变量,若未声明为static存储类别都属于auto变量,存储在动态存储区中。
函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属auto变量
在调用函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。
这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明,但常常省略。
static变量
在函数体内设置一个变量记录函数被调用的次数,这时就希望函数的这个局部变量在函数调用结束后不消失而继续保留原值。
可以将该局部变量声明为static来解决问题。
static变量分配在静态存储区,其生存期是属于整个程序的。
eg:static int count=0; //定义局部静态变量,初始值只会赋值一次
对静态局部变量的说明:
1) 静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不释放。自动
变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。
2) 静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
3) 如果在定义局部变量时不赋初值,则对静态局部变量来说,
编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值
用extern声明全局变量
如果全局变量在函数的外部定义
作用域:从变量定义处开始,到本程序文件的末尾
如果外部变量不在文件的开头定义
作用域:只限于定义处到文件末尾。
如果在定义点前的函数想引用该外部变量
应该在引用之前用关键字extern对该变量作“外部变量声明”
表示该变量是一个已经定义的外部变量。有此声明,就可以从“声明”处起,
合法地使用该外部变量
#include <iostream>
using namespace std;
int max(int x, int y);
int main()
{
extern A, B;
cout<<max(A,B)<<endl;
return 0;
}
int A=13, B=-8;
int max(int x, int y)
{
int z;
z=x>y?x:y;
return(z);
}
C++语言函数间数据传递有两种方式:
按值传递
地址传递
函数参数在按值传递情况的特点
被调函数的形参作为被调函数的局部变量看待。
在内存中分配相应的空间存放主调函数传入的数据。
形参的修改不会引起实参的改变。
函数参数在按值传递情况的特点:
被调函数的形参作为被调函数的局部变量看待
在内存中分配相应的控件存放主调函数传入的数据
形参的修改不会引起实参的改变
指针参数
指针作为函数的参数属于地址传递
特点:
可以通过形参来达到修改实参值
使用 * 指针变量名的形式进行运算
指针传递
作用:将一个变量的地址传至另一个函数中
为什么使用指针做函数参数?
用指针作为函数参数的作用
实现返回多个值。
减少参数传递过程中的数据复制计算量。
通过将数据区的地址传递给函数,使函数能够修改地址对应的数据。
引用参数
属于地址传递,引用变量可以看成是另一个变量的别名。
被调函数对形参做的任何操作都影响了主调函数中的实参变量
引用与指针作为函数参数的比较!!!!!!!!!!!!!!!!!!!
函数的形参作为引用时
作为实参的一个别名来使用,对形参变量的操作就是直接对其相应的实参变量的操作
在内存中不产生实参的副本,是直接对实参操作,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和空间占用都好
函数的形参为指针时
也能达到与使用引用相同的效果。
但是在被调函数中要给形参分配存储单元。
需要重复使用“*指针变量名”的形式进行运算。
数组参数
数组可以作为函数的参数使用进行数据传递
数组用做函数参数有两种形式:
使用数组元素[下标变量]
使用数组名
数组元素作函数实参(传值)
判断一个字符是字母还是其他字符
if(c>='a' && c<='z' || c>='A' && c<='Z')
cout<<c<<"是字母"<<endl;
else
cout<<c<<"是其它字符"<<endl;
数组名作为函数参数(传地址)
数组名做函数参数----地址传递
不带任何下标的数组名代表数组的首地址,即第一个元素的地址
eg:有定义语句
int data[10];
data与&data[0]意义相同
采用数组名作为函数参数:将数组的首地址作为函数参数传递给被调用的函数
数组名作为参数传递的效果
数组名作函数参数时所进行参数传递是地址的复制,也就是把实参数组的首地址赋予形参数组名。
形参数组名取得该首地址之后与实参数组为同一数组,共同拥有一段内存空间。
参数是数组名要注意以下几个方面:
用数组名作函数参数时,形参和实参为同一数组,对形参数组进行的操作,实际上是操作的是实参数组,在上例中,d[i]与data[i]的值完全一样。
形参数组和实参数组的类型必须一致,否则将引起错误。
形参数组的长度可以省略,形参数组不再另外分配内存空间。数组的长度通过其他参数传递获得
定义一个函数返回N*N二维数组元素的两条对角线元素之和
if(i==j || i+j==N-1)
total+=d[i][j];
return total
二维数组名作为参数传递与一维数组名作为参数传递含义相同,属于地址传递,但需注意形参数组的第二维长度不可省略,否则引起不确定性
形参数组的第二维长度不可省略形参数组的第二维长度不可省略形参数组的第二维长度不可省略
形参数组的第二维长度不可省略形参数组的第二维长度不可省略
double sumDiagonal (double [][N] );
函数不能嵌套定义,但可以在一个函数的定义中出现对另一个函数的调用。称为函数的嵌套调用。即在被调函数中又调用其它函数。
汉诺塔
block为n的时候,分三步
第一步:将A中前n-1个block块借助C搬到B中 move(n-1,A,C,B)
第二步:将A中的第n个block块搬到C中 move(1,A,B,C)
第三步:将B中的n-1个block块借助A搬到C中 move(n-1,B,A,C)
那么第一步move(n-1,A,C,B)和第三步move(n-1,B,A,C)的实现又可以重复利用同样的思想,直到n-2,n-3,,,3,2,1就可以直接一步到位如move(1,A,B,C).
#include <iostream>
using namespace std;
void move(int n,char A,char B,char C);
int step;
int main()
{
int n;
cout<<"请输入block数n:"<<endl;
cin>>n;
move(n,'A','B','C');
return 0;
}
void move(int n,char A,char B,char C)
{
if(n==1)
{
step++;
cout<<"["<<step<<"]move 1# from "<<A<<" to "<<C<<endl;
}
else
{
move(n-1,A,C,B);
cout<<"["<<step<<"]move "<<n<<"# from "<<A<<" to "<<B<<endl;
step++;
move(n-1,B,A,C);
}
}
函数重载主要解决功能相近函数的命名问题。
有默认参数的函数时,实参的个数可以与形参的个数不同。
实参未给定的,从形参的默认值得到值。
利用这一特性,可以使函数的使用更加灵活。
本节主要讲解函数重载和带有默认形参值的函数定义的应用
函数重载是指在同一个作用域中,可以有一组具有相同函数名,不同参数列表的函数,这组函数称为函数重载。
函数重载减少函数名的数量,提高程序的可读性。利用函数重载,其上的函数声明如下
带默认形参值的函数
通常函数调用时形参从实参获取具体值,因此实参的个数应与形参相同。
但多次调用同一函数时用相同的实参,C++提供简单的处理办法,设置形参的默认值。
调用时如果给出实参,则采用实参值,否则采用预先给出的默认值。
带默认形参值的函数可以简化编程,提高运行效率。
如下面函数声明:
double area(double r=10.6);
设置形参r的默认值为10.6,调用该函数可以给出实参或者缺省实参。
cout<<area( ); //若实参缺省,相当于area(10.6);
cout<<area(6.5); //若给出实参值,取实际的实参值为6.5
使用带有默认参数的函数时要注意:
1)指定默认值的参数必须放在形参列表中的最右端,即若某个形参值设定了默认值,其该形参的右边所有的形参必须给定默认值;
2)如果函数的定义在函数调用之前,则应在函数定义中给出默认值。
3)如果函数的定义在函数调用之后,则在函数调用之前需要有函数声明,此
时必须在函数声明中给出默认值,在函数定义时不能再给出默认值,即使默认
值相同也不可以。
默认值的参数在声明和定义中只能出现一次。
默认值的参数在声明和定义中只能出现一次。
默认值的参数在声明和定义中只能出现一次。
默认值的参数在声明和定义中只能出现一次。
默认值的参数在声明和定义中只能出现一次。
函数的定义和返回值
任何函数(包括主函数main())都是由函数说明和函数体两部分组成。根据函
数是否需要参数,可将函数分为无参函数和有参函数两种。
所有函数(包括主函数main())都是平行的。一个函数的定义,可以放在程序
中的任意位置,主函数main()之前或之后。但在一个函数的函数体内,不能再
定义另一个函数,即不能嵌套定义。
有参函数的返回值,是通过函数中的return语句来获得的。 为了明确表示不返
回值,可以用“void”定义将函数成“无(空)类型”。
函数的调用
实参的个数、类型和顺序,应该与被调用函数所要求的参数个数、类型和顺
序一致,才能正确地进行数据传递。
函数的递归调用是指,一个函数在它的函数体内,直接或间接地调用它自身
。
值传递:当形参是普通变量,对应的实参可以是普通变量、常量、表达式
地址传递:当形参是指针或数组名或引用变量时,对应的实参可以是普通变
量的地址、数组名、变量
实体属性:数据->数据成员
实体行为:数据的处理->成员函数
class 类名
{
private:
成员表1;
public:
成员表2;
protected:
成员表3;
};
类的成员函数有两种定义方法:
先在类中进行函数声明,再在类外进行函数定义
• 直接在类内部完成函数定义
class Myclock
{
private:
int hp,mp,sp; //指针
public:
void showTime (); //显示时间
void setTime (int h,int m,int s) //手动调整时间
{
hp=h; mp=m; sp=s;
}
}; //end Myclock
void Myclock ::showTime( )
{
cout<<hp<<”: “<<mp<<”: “<<sp<<endl;
}
类的定义要点2
在不同的类中可能存在相同名字的成员函数
在类外部定义成员函数时
必须在成员函数名前给出所属的类名,并使用作用域标识符”::”连接
返回值类型 类名: :函数名 (参数表)
{ …… } //函数体
运算符“::”称为作用域解析运算符 (scope resolution operator),它指出该函数是属于哪一个类的成员函数。
将类看成一个封闭的域,类的数据成员就相当于域中的全局变量,可以被类的成员函数直接使用、共享。
因此在定义函数成员时,无需将数据成员作为函数形参,就可以直接访问
访问限定符(access specifier)
• public(公共的)说明的成员可以从外部进行访问。
• private(私有的)& protected(保护的)说明的成员不能从外部进行访问
protected(保护的)说明的成员不能从外部进行访问。
如果在类体起始点无访问说明符,系统默认定义为私有(private)
访问限定符使用规则
数据成员通常-〉声明-〉private私有
函数成员通常-〉声明-〉public公有
外部语句或函数不能对私有成员进行直接访问操作,所以只能通过调用公有函数来完成对私有成员的操作。
封装:类是一个作用域
域内开放:
类的数据成员可视为域内的全局变量,为所有成员函数共享
类中成员函数可以直接使用类中的任一成员,可以处理数据成员,也可调用函数成员。
域外封闭:
类外部的函数和语句只能访问public公有成员
类是数据类型,对象是该数据类型的变量。
对象的定义
数据类型 变量标识符;
使用对象的成员
类外访问类中public 属性的成员
“对象名.成员名”
调用构造函数进行对象初始化是c++的标准方法
• 对象的赋值
• 为对象的数据成员赋值
• 对象的初始化
• 在对象创建时为对象的数据成员赋值
构造函数的语法要点:
公有成员函数,函数名与类名相同
无函数返回类型说明,注意是什么也不写,也不可写void
一个类可以有多个构造函数(重载)
设计要点:为对象的数据成员赋值
调用:在对象定义时由系统自动调用执行
析构函数de特点
公有成员函数
函数名与类名相同,但在前面加上字符‘~’
析构函数不带任何参数
析构函数无函数返回类型
一个类只有一个析构函数
对象注销时,系统自动调用析构函数。
构造函数的作用:在对象被创建时为对象的数据成员赋值
析构函数的作用:在对象消亡(生命周期结束)对进行善后工作
构造函数与析构函数都由系统自动调用。
如果类说明中没有给出构造函数,则C++编译器
自动给出一个默认的构造函数: 类名( ) {}
如果类说明中没有给出析构函数,则C++编译器
自动给出一个默认的析构函数: ~类名( ) {}
但只要我们定义了一个构造/析构函数,系统就
不会自动生成默认的构造/析构函数。
复制构造函数
• 对象创建时,可用同类对象来初始化新对象
• 这种方式所用的构造函数称为复制构造函数(Copy Constructor)。
Ex:
Myclock clock1(12,30,0);
//用对象clock1初始化clock2
Myclock clock2(clock1)
语法要点:
复制构造函数是一种特殊的构造函数
只有一个形参:本类的对象引用
设计要点:
将形参对象de数据成员值复制给要建立的新对象de数据成员
当用类的一个对象去初始化该类的另一个对象时系统自动调用复制构造函数实现拷贝赋值
若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用复制构造函数
当函数的返回值是类对象时,系统自动调用复制构造函数。
和普通变量一样,同类对象之间可以相互赋值
Ex:
Myclock clock1(12,30,0);
Myclock clock2(clock1);
Myclock clock3;
clock3=clock1;赋值语句,不调用任何函数
如果类说明中没有给出复制构造函数,则C++编译器会自动给出一个默认的复制构造函数,该函数将形参对象的数据成员值原封不动
的复制给新对象。所以,如果对象创建时使用同类对象初始化只是单纯的数据成员复制而不作任何改变,是可以不编写本类的复制构造函数的。
但只要类定义了复制构造函数,系统就不会自动生成默认的复制构造函数。
组合类对象创建时构造函数的调用顺序
先调用对象成员的构造&复制构造函数
依据初始化列表调用对象成员类的构造(复制构造)函数初始化对象成员,完成对象成员的创建
如果初始化列表为空,则调用对象成员类的默认构造函数,完成对象创建。
再调用本类的构造&复制构造函数
进入组合类的构造函数(或者复制构造)的函数体内,执行语句初始化其他数据成员,组合类对象的创建完成
对象成员的私有成员不是组合对象的成员
析构函数的调用循序
先调用本类的析构函数,再先调用对象成员的析构函数,
通过在类A中将外部函数FUN或者其他类 B声明为本类的友元,FUN和B就能够直接使用类A的私有成员
friend 友元类名或函数名;
友元关系是单向的
Ex:类A声明类B是自己的友元,类B的成员函数就可以访问类A的私有成员,但类A的成员函数却不能访问类B的私有成员
友元关系不能传递
Ex:类B是类A的友元,类C是类B的友元,则不能推导出类C是类A的友元
为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。
在C++中,运算符的运算对象是基本数据类型的变量
对象之间运算的实现方法有两种
在类中定义能完成运算的成员函数,然后调用该函数实现运算
对运算符进行重载
运算符可以重载为类的成员函数也可以重载为友元函数,函数名为“operator 重载的运算符”。
运算符重载为类成员函数,类外定义如下:
返回值类型 类名::operator重载的运算符(形参表)
{ …… }
复数加法的实现
class Complex
{
double real, image ;//real: 实部,image虚部
public :
Complex(double r=0.0, double i=0.0);//带默认形参的构造函
数
Complex operator + (Complex c); // 运算符”+”的重载函数
void display();
}; //复数类的声明
//”+”运算符重载函数的类外实现
Complex Complex::operator + (Complex c)
{
Complex temp;
temp.real=real+c.real;
temp.image=image+c.image;
return temp;
}
int main()
{
Complex c1(3.0,1.0) , c2(1.0,4.0) ,c3;
c3=c1+c2; //利用重载后的运算符‘+’完成计算
return 0;
}
运算符重载的实质仍然是函数调用
当C++编译器遇到重载的运算符,就调用其对应的运算符重载函数,并将运算符的操作数转化为运算符重载函数的实参以满足调用需求。
共享类型 实现方法
函数之间 全局变量或者参数传递
一个对象的函数成员之间 类的封装,无需依靠全局变量或参数传递
不同类的对象之间 友元
一个类的不同对象之间 静态成员
静态数据成员属于整个类,而不是某个对象
无论类衍生多少对象,类的静态数据成员只在全局数据区分配一次内存
所以类的所有对象都可以共享它。
静态数据成员de定义
使用存储类型关键
字“static”在类体 -> 在类体外定义或者初始化
内声明
由于静态数据成员不属于任何对象,那么对象创建时就不会为它分配存储空间。为此,静态数据成员在类体内声明后,必须在类体外定义或者初始化,才能完成在全局数据区的存储空间分配。
class Bookcard
{
char id[15]; // 借书卡编号
static int sum; //静态数据成员须在类体中声明 声明时使用存储类型关键字“static”
public:
…………
};//end class
int Bookcard::sum=5045; //静态数据成员必须在类体外定义或者初始化
数据类型 类名::静态数据成员名=值
静态数据成员不属于对象,如果需要在对象创建之前操作静态数据成员,就只能使用静态成员函数
静态数据成员不属于对象,如果需要在对象创建之前操作静态数据成员,就只能使用静态成员函数
静态数据成员不属于对象,如果需要在对象创建之前操作静态数据成员,就只能使用静态成员函数
静态数据成员不属于对象,如果需要在对象创建之前操作静态数据成员,就只能使用静态成员函数
静态成员函数和静态数据成员一样,属于类而不是某一个对象。
静态成员函数只能访问类中的静态成员。
静态函数成员(访问属性为public)的外部访问形式
类对象创建前
类名::静态函数成员名(实际参数表)
类对象创建后
对象名. 静态函数成员名(实际参数表)
类名::静态函数成员名(实际参数表
箭头运算符是用于访问指向对象的指针的成员。
在C++中,使用指针来访问类的成员函数需要使用箭头运算符 "->",而不是点运算符 "."。
这是因为指针本身并没有成员函数,它只是一个指向对象的地址。
【例4.13】 用欧基里德算法(也称辗转法)求两个整数的
最大公约数
假定两个整数分别为num1和num2,最大公约数应当是不
超过其中较小数的一个整数。
辗转法:用num1除以num2,求出余数resd,如果
resd==0,则当前num2就是最大公约数,如果resd!=0,
令num1=num2, num2=resd, 重复以上过程,直到
resd==0为止。
1、设置两个整型变量num1和num2代表两个数;输入num1、num2;
2、辗转法:
2.1、使num1>num2;
2.2、
2.2.1、设置变量resd=num1%num2;
//包含了步骤2.1
2.2.2、if(resd==0)当前num2就是最大公约数;
else { num1=num2, num2=resd;}
重复2.2.1和2.2.2,直到resd==0为止。
可用以下程序段表示:
do{
resd=num1%num2;
if(resd==0) 当前num2就是最大公约数;
else { num1=num2, num2=resd;}
}while (resd!=0);
3、输出当前的num2
,编程计算半径为r的圆的内接正n边形的面积,主函数做输入输出,面积计算函数为CalcArea。可以使用系统(cmath
中)提供的正弦函数,函数原型为:double sin(double X);
#include <iostream>
#include <cmath>
using namespace std;
double CalcArea(double r, int n) {
double pi = acos(-1.0); // 获取π的值,使用cmath中提供的反余弦函数
double angle = 2 * pi / n; // 计算正n边形的每个内角的角度
double s = 2 * r * sin(angle / 2); // 计算正n边形的边长
double area = (n * s * r) / 2; // 计算正n边形的面积
return area;
}
int main() {
double r;
int n;
cout << "请输入半径r和正整数n:" << endl;
cin >> r >> n;
double area = CalcArea(r, n);
cout << "半径为" << r << "的圆的内接正" << n << "边形的面积为:" << area << endl;
return 0;
}
设计一个分数类Fraction该类的数据成员包括分子 fz 和分母 fm;类中还包括如下成员函数: (1)构造函数,用于初始化分子和分母,并进行约分。
(2)成员函数 print,将分数以 "fz/fm" 的形式输出。
再编写主函数对该类进行测试。
#include <iostream>
using namespace std;
// 计算最大公约数
int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
class Fraction {
private:
int fz; // 分子
int fm; // 分母
public:
// 构造函数
Fraction(int fz, int fm) {
int g = gcd(fz, fm); // 求分子分母的最大公约数
this->fz = fz / g; // 约分后的分子
this->fm = fm / g; // 约分后的分母
}
// 输出分数
void print() {
cout << fz << "/" << fm << endl;
}
};
int main() {
Fraction f(6, 8);
f.print(); // 输出 3/4
return 0;
}