帖子摘要: ?主页我的代码爱吃辣 知识讲解C 创作者我的代码爱吃辣 ??开发环境Visual Studio 2022 前言补充C语言语法的不足以及C是如何对C语言设计不合理的地方进行......
1 Q' F& \# v% N* F
Z( e+ X4 F6 v: [6 u 大家好,欢迎来到Java吧(www.java8.com),交流、学习Java技术、获取Java资源无任何套路,今天说一说:“C++——函数重载,引用” % E @+ l4 U) M* I" g
2 i+ i% F0 N/ J
6 E- v9 {2 u) |( r
0 U C0 ]( f3 T
" P1 P, d: Q/ } V . Y! ^, X" g- N2 D
?主页我的代码爱吃辣. D# |1 y" s4 p7 P' O
知识讲解C++
* G H+ s1 s5 s 创作者我的代码爱吃辣
5 L" Q' V# Y- ^6 h% @ ??开发环境Visual Studio 2022+ j4 _8 R! W- [$ r0 R8 i
前言补充C语言语法的不足以及C++是如何对C语言设计不合理的地方进行优化的比如作用域方面、IO方面、函数方面、指针方面、宏方面等。' h( g" e$ j3 t1 b! e% V/ G2 ^
& Z* r2 T+ q2 q, ^
8 W: S* r) B2 H! B& e: o
目录
! S2 O" J. i# D& v- n 2 ?+ I4 A* z' L2 D( @- n
一.函数重载
' L1 x% u/ `1 u7 l" j0 s ( B7 N1 }! N7 v& b0 N- o- x& v
(1)函数重载概念
* \5 a4 M+ \4 F7 l
9 b0 c O3 f% |; B% k0 b- Y: x 1.参数类型不同
+ r% O! v( w+ k7 y* M. w ) z, {, x/ p t
2.参数个数不同
/ T ^5 p" l1 S 6 e0 f: U2 |2 S: L
3.参数类型顺序不同
7 U2 Q( l2 e3 i+ e' ?3 o
4 @* s4 o% U6 N+ H/ \- G (2)C++函数重载的原理7 p( k m2 A( }
7 h% y. W9 z! y2 C0 z
二.引用
6 B: Q2 b2 W0 ~, K! F' Q
: A: a( [" P% O5 V$ [' P+ q" l 1引用的概念' Y& f( \9 e% E1 q$ e9 c
, a' i+ O* ?" s' x$ K 2引用特征& k9 g- J% R6 N2 n8 S2 E/ [ s
# S( b" l0 O+ _, a) i, l) T6 m+ Z5 s
1.引用在定义时必须初始化
& G! K( T3 `: r) Z- K; \
" Q+ X2 J# ]+ J: o q0 Z8 Z 2.一个变量可以有多个引用
6 C G. F S9 R/ F( K4 Q
0 Y% c$ }, N( B# E 3.引用一旦引用一个实体再不能引用其他实体
7 l8 r, o; v" F6 [1 G2 l, Z: I' d
4 G! V5 x4 ?. ]/ { p3 j 3常引用
" M4 K9 ^5 }+ Q* p+ M5 i
+ u3 @2 {2 L- B7 m6 U 4 使用场景+ A# r/ w9 |. @! F6 h
, O i8 ~2 Q" g% e 1.做参数
" Q) C+ A' i, A7 _ 5 }/ F& I2 n1 ~+ B- ]
2.作为返回值
d) H& R! n6 }! v' N# L! C p/ g D) E6 s* {. Y$ S: ~
5传值、传引用效率比较
0 P7 ~" J8 Z! A: ]! p' v. }
# e2 k( W& q5 r2 p; L9 q 6引用和指针的区别
/ D3 |8 N a m; x; g( U
5 E- Y5 S$ \2 k4 I 一.函数重载 * k7 V! @, Q4 |1 V$ o. p+ Z
在自然语言中一个词可以有多重含义人们可以通过上下文来判断该词真实的含义即该词被重
z5 M5 I& B* g4 [9 q7 S/ S 载了。# D, r" B2 N- q+ m
比如以前有一个笑话国有两个体育项目大家根本不用看也不用担心。一个是乒乓球一个
. b8 E V+ b# L" K+ [ 是男足。前者是“谁也赢不了”后者是“谁也赢不了”
* l. y0 k/ |& l" a# z - F0 T" A9 E$ x( [" T) t g) t- e
(1)函数重载概念 " _; ~0 U3 s1 b
函数重载是函数的一种特殊情况C++允许在同一作用域中声明几个功能类似的同名函数这 B6 a9 u/ ~ v% v5 O
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同常用来处理实现功能类似数据类型$ S" K' W! i' w/ n+ y9 f
不同的问题。
5 g0 l# [- G' c5 w) m
- r% F7 A1 ]( j5 \ 1.参数类型不同
# {! ~( z3 {) o8 f
int Add(int left, int right)
{
# i7 P4 z: Q' Y ^* D% t0 | cout
1 q# G, {, ^6 K: |( A0 @" Z [img]/f51b080ce5564d6a88f6348a162ac709.png[/img]
6 R1 o/ _8 C3 J" _8 K ?8 H {
2.参数个数不同
[code]void f()
{
4 h4 j( S& b2 T5 X8 K% \# n cout
3.参数类型顺序不同
[code]void f(int a, char b)
{
4 z; [% O, X5 h$ h! W1 X, ^0 ~ cout
(2)C++函数重载的原理[/size]
我们知道C语言不支持函数重载为什么C语言不支持函数重载呢当有两个函数名相同的函数时C语言就已经无法区分了是因为C语言仅仅就是通过函数名来区分每个函数。但是在C++里面C++通过对函数名配合参数进行修饰就可以通过函数名以及函数参数特点对每个函数进行区分。
2 \- t: y/ i* G; {( f4 B [b]通过这里就理解了C语言没办法支持重载因为同名函数没办法区分。而C++是通过函数修
! x$ Y' v' ]* J0 I; v 饰规则来区分只要参数不同修饰出来的名字就不一样就支持了重载。[/b]
1 N# Q) j M I0 h
[b]如果两个函数函数名和参数都是一样的仅仅是返回值不同是不构成重载的因为调用时编译器没办法区分。[/b]
2 y, c1 J" V! f$ k, H& } 二.引用
1引用的概念[/size]
- `5 E7 _) x3 U ?- E& q [b]引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空
- ~6 I+ ~# `; K) \" i$ m4 G! @( ~ 间它和它引用的变量共用同一块内存空间。[/b]
) ~" i4 C- e& g( ]4 I# G1 z
; x3 @: S9 r. _! @( F 比如李逵在家称为"铁牛"江湖上人称"黑旋风"。
* \# a# R$ g% D) d0 u
- U* \4 n$ q8 ^. l3 n [ [b] 类型& 引用变量名(对象名) = 引用实体[/b]
0 x& ~6 `/ n5 W! d4 q
* ~5 V, R! {- w* B% ]9 G 看一段代码
4 `+ j9 R. \- N/ a [code]#include[i]
using namespace std;
int main()
{
int a = 10;
int& ra = a;
std::cout
; Z' B( l I7 P$ I+ z [img]/085dfbe83c9e4a998628464eef06214c.png[/img]
) m. g+ F- h) e& ^0 ^$ o, |6 [) w' Z
+ I% P! z3 P' T8 H- h3 C [b]引用和对象使用同一块空间:[/b]
[code]int main()
{
int a = 10;
int& ra = a;//
; S j0 @2 Y* J: h [img]/d1dded1f21764a8f81abab2a4231f512.png[/img]
5 U- E: e' x5 _" o' ?
$ J0 j, T1 o% l$ b3 E0 D 大家还记得这段代码吗
0 ]# i }0 a/ F8 ^( y! n [code]void ListPushBank(ListNode**phead,int x)
- u' B/ i! j1 A4 e/ n* ?+ q; J {
7 b) k* { Q8 N1 F9 F! |! N9 p8 T, J6 ^ ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
newhead->val = x;
newhead->next = NULL;
if (*phead == NULL)
9 f9 [. H4 C& g( y; g$ _% S2 V8 Z {
*phead = newhead;
! G# a$ g5 {8 B }
else
+ q! n, _0 u6 j- P. X' q/ T; f5 H {
2 E& F( L! ?( h7 B" Z5 } ListNode* Tail = *phead;
while (Tail->next)
{
' l6 y2 @/ @! n$ _# x, x$ { Tail = Tail->next;
8 _, h% O b: `5 G! R }
Tail->next = newhead;
}
}
int main()
{
ListNode* head=NULL;
ListPushBank(&head,100);
ListPushBank(&head, 200);
0 S7 g6 R2 d; O& ?9 E return 0;
} 复制代码
, n2 D* K0 k. u) C0 a3 h$ _; S1 M' t
在PushBank时如果我们遇到第一个结点时就需要把第一个结点作为头结点此时需要更改函数外面的 head 指针就必须要 head 的地址在函数里面就得使用二级指针。
/ h7 r& M) ]( c s9 I) B* C ( I( l8 l7 @! P+ P3 h
现在我们有了引用就可以很方便的解决这个问题1 a/ n0 p4 C! r% m
/ L, A9 m4 V- j3 T0 g5 r9 Q7 k void ListPushBank(ListNode*& phead, int x)
2 k5 F5 X8 G' Q$ | {
: V! W) P# ~2 O4 I0 f( w6 d: P ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
newhead->val = x;
newhead->next = NULL;
if (phead == NULL)
{
phead = newhead;
; ~9 {# W2 Z/ k }
- `. @0 D" g8 e else
{
3 ]" v; ]+ M/ M4 X ListNode* Tail = phead;
" M0 }* n' ]" Y+ y5 Z while (Tail->next)
{
Tail = Tail->next;
5 b# j$ `2 L2 [: e6 j8 U: t }
* i- p# |& `) J: J1 n9 V) a. Y8 M Tail->next = newhead;
) q( c% D2 n9 r# d: ^) U }
/ u# V* k4 s5 K$ A- U }
3 Z' _" Y7 `! T) b1 `. I int main()
& t l, U* z$ C. \6 I {
" j5 h( v+ |+ N: M# n ListNode* head = NULL;
( U) V0 w, K+ J* D. Q ListPushBank(head, 100);
* E7 z# `# h! g2 E* l) c/ |, e ListPushBank(head, 200);
, p* s. F5 U0 [, n2 s; E! D
return 0;
& U$ L; e7 ?+ h; g } 复制代码
' Q" _2 _$ \- _& @" O 2引用特征
" i) W1 G! R2 f/ z- {; Q 引用有一些很重要的特征需要我们注意
) c) _9 `* [ o, G2 I$ Q5 F" ~ 2 W$ g, X, [- T
1.引用在定义时必须初始化
* ?& |! F1 s* M- }! _% }& z
3 W8 C: u! }+ n
+ c: K' |6 j3 d7 |6 P1 f 这里不难可能出语法直接时报错的。
% Q0 D" `9 Z1 u+ K; i4 a( M! [
% l }: X8 j& n# E 2.一个变量可以有多个引用 ' G* U4 D6 o; s7 M+ @# d2 H
nt main()
! Z0 t2 I* _+ L+ e {
3 ]# Z8 a# r8 M' m% F" f9 n9 [2 Z int a = 10;
& U: n: k& u4 i1 f! [% r* g" Z5 ? int& ra = a;
int& rb = a;
" U& e l/ Q0 x- L% H, a int& rc = a;
printf("ra=%d,rb=%d,rc=%d\n",ra,rb,rc);
& \8 S0 e, {) J7 \- O& P f return 0;
$ Q; }2 v8 u. Z6 ?& q- R* m" c } 复制代码
( L0 }. r) S( [% ]" d
! p9 B& S, O3 U' {5 o, V. x' n - g2 S% Y) m( u. E' V/ X! P0 q6 C
0 u7 |6 M& U9 Z1 H: } 一个变量可以有多个引用并且多个引用都共用一个地址空间。1 K; P' t0 h/ s0 t
7 o( K) l0 v3 B! D: \/ Z
5 Z- b6 |! d7 b: ^
3.引用一旦引用一个实体再不能引用其他实体 - |" m) `: O# z2 W: ~
int main()
, Z7 f. {8 o7 T; O% N' q {
4 m J7 U( Y, f int a = 10;
int b = 30;
int& ra = a;
ra = b;//仅仅只是把b的值赋值给了ra
std::cout
[img]/2701e3ac5b934260b5bcb2086c6418bd.png[/img]
[b]
. i2 |* z9 s- [ b: w/ @1 [7 o( `# T [b] a与ra 仍然公用一块空间ra = b并没有改变ra的引用实体实际上也改变不了ra的引用实体。[/b]
, w0 F |# w; i3 n7 O [/b]
0 S/ D# S# J) k: P2 f/ B. k 3常引用[/size]
' f. o$ q* l% e0 `1 { [b]变量权限可缩小或平移但不可放大[/b]
' X: z* j6 W. X0 O6 _' C; }# |
[b] 场景一[/b]
[img]/04bbea8e44e74732b3376d0f79ff596b.png[/img]
* N, |" k' _4 C, J' w1 ^% G [b]
: m1 @& y/ G, W# N [b] 因为常量只具有可读属性如果引用成功的话就可以通过引用来实现对变量的修改变量权限就是放大了这样也是说不通的。[/b]
6 f* p6 k; [! {) m f; D
[/b]
但是也不是没有办法使得引用的实体是常量。
. {1 x' x' h2 R* b J5 j
[img]/c776fa352b284427a1dd3f60d49b1775.png[/img]
/ s: H1 d `, d" U
{9 A v; t7 K [b]
[b] 变量是只可读的变量的引用对象也只能是可读的这也就是变量权限平移.[/b]
+ D& t6 F3 k( M- k# ]3 G
[/b]
. T) g. c& U ~$ r 上述的结论不仅针对引用对指针也是有着同样的效果
5 b! G# K. ?& F; w) O7 H
[img]/14583405934f4946b983152b595a9570.png[/img]
0 K" V: Y/ o. |9 F
[b] 场景二[/b]
; \4 n9 ~, N0 N- b% W% c
4 P( \. o: k% `! u& ?* g [img]/8ad30a1367dd4a9785bc0596212141ec.png[/img]
[b]
/ b/ {, s# j. h4 j [ol][*]这里的报错大家是不是认为是类型的原因导致的但是往后看加上一个const就没有报错了而且仍然是int类型的引用。[*][b]首先来了解类型转的原理类型转换是有一个中间变量的而引用的对象的就是那个临时变量因为临时变量具有常属性所以加上一个 const 就可以消除报错。[/b][/ol]
1 x1 x8 P; ` X; D& _6 U [/b]
4 使用场景[/size]
. O# A0 X1 M( Y" y4 Z4 j$ M, J 1.做参数
我们之前使用指针实现函数来交换两个变量的值
3 Z1 e5 E/ `( W6 f) g% {
[code]void Swap(int* pleft, int* pright)
{
int tmp = *pleft;
; e9 g0 y) `) i5 x T4 B *pleft = *pright;
*pright = tmp;
4 ]4 T: t/ ~, i2 m6 b# f* b } 复制代码
# B7 H4 B8 ]/ y& l) K6 u. w 原理我就不多解释了想必大家都已经熟记于心了今天我们用引用也可以实现
5 _& d) z. p: `; _; v7 r% I* D0 r
( c* M2 e% Y" f [code]void Swap(int& left, int& right)
$ n9 O q5 W. I C6 \* H: l {
$ W% y0 Z8 l3 B) o int temp = left;
; N; R; I6 ?2 P9 ?! `5 l/ l5 q: z left = right;# e U- r. V. I+ I* a* S
right = temp;
E+ O8 L9 C0 S) ]) @( g }
2 u/ b, f, U: H6 q int main()) ~5 D$ A0 w @0 O
{2 X% M! e6 l9 P$ I- g* [. I) z, E
int a = 10; int b = 20;
% g, T Y# f C% ]0 [ std::cout 3 E8 \' @9 v* c1 b$ c2 S6 B
0 u4 {6 H8 m: Y, o7 T- s9 n
9 S# ~9 e2 }; X9 [. {" w 2.作为返回值
5 q7 W% k/ K3 b1 x2 K3 [: k [code]int& add(int a, int b)) D V- S2 Q( \6 H3 g' m
{
& m' G* |# P! z \+ h5 V4 k int sum = a + b;
+ i# D' Y% `6 O9 g return sum;& b" ?: o5 x4 F2 {! y
}
6 F) d4 @0 t2 }! @' g0 D; ]! |; H int main()$ B! p" X& N8 q I5 L7 Z* J. K
{. ^3 O3 ^/ j6 _$ `* o9 J. A) @
int& ret = add(1, 2);
6 G! v) D' }( A* z std::cout
/ F: c! ~3 d2 x; J0 S # C ^: f3 A$ N2 l! _0 O. i9 R! @ {
4 C0 ]+ T3 N" t2 B+ N" j; p
问题来了我们只对ret进行了一次赋值但是两次输出却是两个值。这又是为什么呢+ }" x+ @3 T3 @3 a: d( @
/ h9 r8 `! O7 L4 ]$ w8 L
) Y6 s" _% @" y1 O7 K1 W7 {8 z
2 d' Y2 p/ B# i4 j
% S& W% F5 y6 F# _" }# f2 v9 W
怎样可以返回引用呢其实只要出了函数作用域函数的返回值还在就可以返回引用。 ( L1 [6 d9 P' ~. X+ X) b
) `2 z" i/ m: ~
例如static 修饰的变量存储在静态区不会因为函数栈帧的销毁而销毁。
3 @/ h- o0 R9 w 9 D) [4 N8 T1 s5 V6 `! s
2 ~, |( U W" ~
. m0 c! q$ \0 R [code]2 _2 R+ k9 h1 ^2 M, X
int& add(int a, int b)1 D u% n( _' l) V" X- W8 D
{
' M1 \; b7 _9 S2 M* c static int sum = a + b;% H/ o+ \: @0 B
return sum;
$ u- ]3 v% a# j$ P }
) {, Z o8 i* f: j int main()
9 o T W1 X& z# _0 t7 C {
: w) S: R+ e7 t& N. O2 q int& ret = add(1, 2);
8 O) b/ v) U5 E% n) t! P: v std::cout 2 W/ F6 a( p$ e1 z4 M; r7 G
8 H, i/ l3 n$ l! B/ _. Q- p
: {8 _6 E1 H0 X! Y. A1 l/ L
& }0 c) u; q+ R# M6 E% n $ e) U5 q8 \, f5 Z/ d4 Y/ b( U
注意如果函数返回时出了函数作用域如果返回对象还在(还没还给系统)则可以使用引用返回如果已经还给系统了则必须使用传值返回。
( r$ w+ n' U# R$ P2 o
5 x% y% X( _5 c! u5 W- z% R ' W# z7 O/ R3 p& C: x3 V
5传值、传引用效率比较
- a! A0 A! Z) O) K0 f+ x( r 以值作为参数或者返回值类型在传参和返回期间函数不会直接传递实参或者将变量本身直
' E: u3 G3 K: K! | 接返回而是传递实参或者返回变量的一份临时的拷贝因此用值作为参数或者返回值类型效
& ~2 M! E+ a. r- h 率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低。
( c, x9 \3 f8 n2 B) b
; v5 `" a+ q+ p: Z. ~9 N7 y [code]struct A { int a[10000]; };
* P: r' R; z: x$ f& _, l/ L void TestFunc1(A a) {}
7 m" y6 D4 ^! v, u& V5 w! r' b void TestFunc2(A& a) {}+ L" n& o S- s
void TestRefAndValue()
; f$ h, g, D' V3 C {
+ o2 G" \0 ]# e$ K$ D7 @' t$ R A a;
7 Y6 O/ i2 C. T" W$ J$ L4 B // 以值作为函数参数) K/ a/ K/ M: O2 K
size_t begin1 = clock();: {* F Z( s& ]
for (size_t i = 0; i
$ F; I' w, r- F
7 o! `2 c8 A Z- L# ^- K4 P
% `0 { L( K1 l- A [code]struct A { int a[10000]; };
$ g: {! F6 w, y A a;7 r6 A7 f$ s2 K2 }$ A8 l
// 值返回9 e/ H. ~) m, N+ v- h/ S5 E
A TestFunc1() { return a; }( w' ^9 S; I9 X+ h" M9 E
// 引用返回* E6 |4 N ^8 \4 Y3 i' d
A& TestFunc2() { return a; }
1 K) d$ M' X' n0 h: M; f void TestReturnByRefOrValue()% h4 T4 Y" @5 a* `' v7 W
{# M+ _/ y: w' H% r- ]4 L6 s b
// 以值作为函数的返回值类型 S1 q) G9 A5 j0 z
size_t begin1 = clock();, k# t, M% F$ h) I) z
for (size_t i = 0; i
' r: h3 V: J1 Z% p8 w$ `; }1 S 1 _/ n) \0 N! ^, ]0 K
+ P4 R: }. Q' q- J* {& C0 o 通过上述代码的比较发现传值和传引用在作为传参以及返回值类型上效率相差很大。 9 j+ G6 `- T' S) q/ q
& f$ `- a9 B9 O: T: H5 }
6引用和指针的区别 ! [+ w" f! R+ B7 ?$ K- e; `
$ Q2 Q7 ~2 k2 i% I& e 在语法概念上引用就是一个别名没有独立空间和其引用实体共用同一块空间。 在底层实现上实际是有空间的因为引用是按照指针方式来实现的。
, y5 V/ Q. ?! C* ? " L( a! R; l1 \5 P8 ?
+ y0 t6 f/ O& v0 A8 ~
, a1 s9 r8 R: I& g 引用和指针的不同点:
) M+ D7 ^2 A) ~, Q1 _ : A2 @* @, H% l
* Z r! s3 O. R, i
[ol] 引用概念上定义一个变量的别名指针存储一个变量地址。引用在定义时必须初始化指针没有要求。 引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体。 没有NULL引用但有NULL指针。 在sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32 位平台下占4个字节)。 引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小。 有多级指针但是没有多级引用。 访问实体方式不同指针需要显式解引用引用编译器自己处理。 引用比指针使用起来相对更安全。[/ol]' P/ l3 v- D* i$ Z
( C3 Q1 [) h ]* T/ ~! M- E5 |
0 y0 ~$ u }$ E5 p, M ; R4 a# y, t. O
本文来源csdn,由Java吧转载发布,观点不代表Java吧的立场,转载请标明来源出处:https://www.java8.com