|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
尊重函数接口,尽量不作内部改动9 t. d! }% z1 W* K- ]) A, I8 R! f
C++代码语句分为:内置类型,名字,变量,操作符,标量,字符串,预处理指示(如#include)等
* L; C/ m+ e/ N3 F, F0 P$ N0 u
' q8 `5 N% |4 A' e3 zC++中定义类来组织数据结构
0 v. n$ D8 r r7 j6 k5 E/ G
5 B7 g t+ l* o% s- D标准库的头文件用尖括号 < > 括起来,非标准库的头文件用双引号 ” ” 括起来。' q7 Y& {0 u0 D% Y1 Q4 t3 F9 J4 i
对象是内存中具有类型的区域。
' c @4 \9 x& d$ B9 I& ?# E. e; O L, H, K
在C++中,初始化和赋值是两种不同的操作。
9 i9 D4 }, w7 W2 }% A @) P! n5 b
内置类型变量在任何函数外如不初始化,系统都会初始化为0,在函数体内如不初始化则可能发生错误(除了用作左操作数)。
) }: \$ a* B* j# B; \
) g' Z9 y( k. k有些类类型有默认构造函数,因此定义其对象时可以不显式地提供初始化。
/ {9 A1 Q. y h( C) E2 ]在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量,声明变量不分配内存,因此声名不初始化,若声明同时初始化则视为定义。3 f6 H' P! B1 o
extern关键字用于声明。全局域定义的非 const 变量默认为 extern。要使 const 变量能够在其他的文件中访问,必须地指定它为 extern。3
" S. d4 @5 l8 n3 }0 H# g, o3 k$ j' u2 M, L3 k
要理解的重要概念是引用只是对象的另一名字,初始化是指明引用指向哪个对象的唯一方法。如int val = 1024;int &refval = val;//refval是val的引用,当int I = refval;相当于int I = val;引用是一种复合类型。enum是枚举关键字enum Points{point2d,point2w,point3d,point3w};表示花括号内成员默认为0,1,2,3都是const变量,用来初始化枚举成员的值必须是一个常量表达式,但枚举类型的对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其他对象来进行。Points是一个枚举类型。+ u5 ~! U$ U6 J. u9 X* Z
5 O0 `% o$ [# |C++ 中,通过定义类来自定义数据类型。类定义了该类型的对象包含的数据和该类型的对象可以执行的操作。接口(inteRFace)和实现(implement)。
8 w% l: C5 l: E6 l( Y7 ~! P
) O& s6 Y$ F1 l# @9 {定义变量和定义数据成员存在非常重要的区别,类中定义的变量称为数据成员,当定义数据成员时,只能指定该数据成员的名字和类型。类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数的特殊成员函数控制初始化。! J' N G# l: p
8 ?) \1 K5 \* Y. j: W, d4 ^: ]- k) G4 O
% v$ A- l6 u% MC++中也可以使用struct关键字来定义类,它与class的区别在于类中第一个访问标号前的成员默认为public,而class默认为private。注意private只是对于类外语句调用权限而言,在类内部成员函数可以随意调用private成员。头文件用于声明而不是定义,因为定义只可以出现一次,而头文件在多个源文件中出现,所以只用于声明。对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的 const 对象和 inline 函数。头文件中含有支持分别编译所需的类定义及变量和函数的声明。C++预处理器是在编译之前运行带有预处理标志#的程序,如#include指示允许两种形式<>和“”,前者表示标准库头文件,后者表示自定义头文件。#define预处理器变量,通常全部大写字母。可以用:5 ~" Y) u$ _# @5 M7 }# c( F+ l Q0 E4 @1 ~& N, S
1 g; s5 {0 j, U6 k, T3 E p5 b
- ; _" Q# ?' J2 K$ S4 ^8 |. R- |( v C6 T: y
8 _1 x6 V3 D5 P4 L% Z, M
#ifndef SALESITEM_H* [1 ~$ h# L1 t/ S, d
2 n# N7 o0 j% p% s& H1 L9 w#define SALESITEM_H// Definition of Sales_itEMClass and related functions goes here#endif1234
. \2 y$ i2 l& X+ T- L3 u! c; q$ w# S8 J
6 J' ]' ]1 Y( o' Y2 Y2 E% \首先第一句判断SALESITEM_H是否定义,若没有则第二句定义该预处理器变量,直到#endif结束。若第一句判断出SALESITEM_H已经定义,则忽略后面的内容。此预处理命令可以用于避免头文件在被程序多次包含时内部定义的类被反复定义,引起编译错误。" B2 U) S" Q) a* X! n0 Y/ p; Z
5 j, b5 K+ S7 U8 ^& b: O2 H1 ^- Y
( v" ?8 y" O! A" d* T! P* KString类型的输入操作符<<:忽略开头的空白符(空格,制表符,换行符等),读到该字符串第一次出现空白符终止。字符串字面值包含一个额外的空字符用于结束字符串,所以“string”包含7个字符。
3 [% {1 g: c* T# [/ u- S$ d+ T; R1 J+ m
# A5 d# G* C# R# X6 E0 a
6 @3 R' h {! |& V先初略地了解一下vector和iterator的概念,以后用到时深入学习,vector容器是一种类型,vector ivec表示保存int对象的类模板。Iterator也是一个类,vector::iterator iter;表示由vector定义的迭代器类型对象iter,用于遍历容器中的元素。使用它们之前必须在文件头包含并using声明。) x: T- u8 U5 J
' ]) J! o" N7 U& k; j T1 c* j5 i& s' _1 D9 K5 E& G3 t, h. o& A
C++ primer第三章介绍了几个常用的标准库类:vector,string,iterator和bitset。
8 I# _$ Z% l+ sC++ 语言提供了两种类似于 vector 和迭代器类型的低级复合类型——数组和指针。现代 C++ 程序应尽量使用 vector 和迭代器类型,而避免使用低级的数组和指针。设计良好的程序只有在强调速度时才在类实现的内部使用数组和指针。* b4 `# Z/ E1 W9 I: i- w5 Q
4 T# e o% P5 p) ?* i, k1 N1 V" n1 u3 W& q4 B
数组定义的类型名可以是任意内置数据类型或类类型,数组元素可以是除了引用之外的任意复合类型。数组的维数必须用值大于等于1的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型 const 对象。非 const 变量以及要到运行阶段才知道其值的 const 变量都不能用于定义数组的维数。' O4 e u, I U8 w a" F3 T3 m+ U; o
( ~2 Z$ e- V! i+ ?7 r与vector不同,数组不允许用另一个数组赋值和初始化,数组一经定义就不允许再添加新元素。$ r. z; _0 z8 u3 [& A
指针的定义:int *p;从右往左读,定义p为一个指向int类型对象的指针变量。一个有效的指针必然是以下三种状态之一:保存一个对象的地址;指向某个对象后面的另一个对象;0值。避免使用未初始化的指针。- t0 W& W) M; V# R+ W7 Y% { t5 B4 F8 I7 }& O7 G
指针的算术操作:加减整型数值。与迭代器的算术操作实现方式相同。对指针解引用,可得到它所指向的对象的值:*p。在表达式中使用数组名时,实际上是使用了指向该数组第一个元素的指针,注意数组名与指针变量的等价性。+ V7 d+ M) E f/ V* ]8 G
" [6 [, @5 ?9 ?8 e1 j5 x9 ]3 _6 h7 e% D0 D# A
C++ 允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。如:
6 F; T- a% S% S+ F- `
/ G, ^* ^. f# {* v8 }/ ]8 S+ j* f% z
const size_t arr_size = 5;int arr[arr_size] = {0,1,2,3,4};int *p = arr;int *p2 = p + arr_size;1234
. I7 ^9 b5 e$ y G; y K7 Wp2保存的是数组arr_size超出末尾的地址。* C, T% Q2 [( S8 Z, }) q6 H8 E5 v, U9 q* e' Y$ r
指向const的指针理解为:“自以为指向const的指针”,当一个指针定义时指向const对象,它就会认为自己是一直指向const对象的指针,即使它后来指向一个非const对象,也不能通过引用修改该对象的值。但可以通过重新定义一个指向该对象的指针修改值。在实际的程序中,指向 const 的指针常用作函数的形参。4 K" R) k- f* D" Q
2 q& o- P7 W: P
1 E: @6 E7 k7 l: E- m, w3 G$ r另外还有const指针,如int *const p = &val;const指针若指向const对象,则限制一切改动的行为。8 Z9 q% e5 G# G# Q- T+ H
位操作符<<和>>分别代表二进制数各位整体左移或右移右操作数位数。如int 12>>1;表示1100变为0110(6)。赋值操作返回左值,具有右结合性。! C. a' t' B. V X0 _
- Y# G2 T6 _# J2 j3 Q N9 X( e, `2 R2 z8 e1 u$ Z3 m
j = i++与j = ++i的区别,前者自增操作符返回初值,后者返回自增后的值。尽量使用前置自增。. `. n9 ]- Y& U* |/ M" w U! X8 f* y' m' \4 G, _
! o1 ~, W- C9 K
5 E) J8 l, S& f6 v, ~1 r J4 ]( ZC++语言为包含点操作符和解引用操作符的表达式提供了一个同义词:->箭头操作符。2 J& h5 t8 q& c3 E# M
4 R/ D9 v& R9 \ Q2 ^8 S- }' p# W( o5 t2 r' j$ }6 L
如:sales_item *sp = &item1; (*sp).same_isbn(item2);等效于sp->same_isbn(item2);- a* _: z0 r6 A, p: K; @
运用new和delete语句创建和撤销动态内存,即可创建和释放动态数组也可创建和释放单个对象,如:int *pi = new int; 表示在自由存储区(堆)分配了一个整型对象,并返回该对象的地址,并用该地址初始化指针pi。
2 t' W% k. h1 x: I, _- N) O6 b$ ]' h+ Z! p$ Y
8 R3 V W3 g; \* H3 |又如:int *pi = new int(20); 同时初始化该整型对象为20。string *ps = new string; string的默认构造函数将其初始化为一个空字符串。若要显式初始化非默认初始化的对象,可写为:
) n0 N& |: A8 F% \( V. I7 h0 w! b5 Y: b/ e
3 b1 k# g% q, Cint *pi = new int();在类型名后面加圆括号表示初始化为空值,这样做可以避免因未初始化带来的错误。, Q$ Y8 z' I ^2 K
: ~" a7 H5 h& i* C% Ddelete pi; 表示释放pi所指向内存,但pi中保存的地址仍存在,此时pi成为悬垂指针,易发生错误,应立即将pi置0,以显示它未指向任何对象。使用delete删除非自由存储区的内存是不合法的。: `8 u# D# M- B7 [
' q4 U, t2 u7 F$ f* q4 d9 c/ \8 O
$ ~- a2 B5 w' ^9 Q对于const对象,须返回const对象地址,如:const int *pi = new const int(1024);0 Z# c n: `" n' N; K) m
& ?# V3 m6 } i: L4 A a( }) SC风格字符串是一个const char型字符数组:const char *ps = “C style”; 以空字符为结束位;C++标准库用string类型重新定义了字符串,更加简单直观:string ps(“characters string”);9 X8 U$ J! n8 F g8 _7 V( Q* s7 J$ S6 L' H7 x7 i; Q
1 u7 y+ ^, j: r5 Z+ W. s: w' i" G# f3 e4 O
尽量使用string类来定义字符串。* G8 m7 I; X2 Q
/ m4 `$ D( O' Q9 ]+ a2 `C++编译器在隐式类型转换时会尽可能防止精度损失。强制类型转换的一般格式为:* ?( e$ x& ~7 D) k+ A+ o( l5 r
3 ]5 H: ^$ I- s3 N5 w; C- z7 {$ W. Q( o0 i% b3 V, r& X, ?. c; k% Q! t7 Z
cast-name(expression) ;其中cast-name是要强制转换的方式,如static_cast;
T/ T) @- j, l+ X
. i' u O( {# g0 d$ Mconst_cast 等,type是欲转换为的类型。尽量避免使用强制类型转换。+ e8 E z F1 h f+ d# w
9 S$ V: L2 Z) S e9 e+ }+ E8 jswitch语句每个case后要加break,否则程序只会跳过后续的case标号继续执行case标号内的内容。若要在case中定义变量,则使用花括号限定变量的使用范围。
" Z- E2 u8 F# `! c* j* }C++异常处理关键字:throw和try{}catch{}catch{}…;了解一下,throw用于退出代码块,转向异常处理。try一段代码,catch其中的语句,作相应处理。标准库定义的异常处理类都在stdexcept头文件中。) v! e+ n7 j% _: _# L8 P/ F
函数形参使用引用修改实参的值安全而方便,尽量不使用指针。
9 b% q8 t- Q& a/ o. s& _- S, T2 e2 u8 }" K. h3 @) T) J! a; t
函数中如不需要修改实参的值,则统一使用const形参引用,如:下列程序在s中查找c字符:- ~% d- S. u/ ~9 \' M; o# A1 W7 O: \/ |9 N7 [, G) m5 ?
6 E2 `' _5 m: \# t) b3 z
) U+ Q+ w* m' F6 P3 g- $ x) K( U3 S4 B- E( V4 M# A2 X$ p, ]: E6 F# }0 c
4 t* W, y: G3 p, A3 O6 X string::size_type find_char(string &s,char c){ string::size_type i = 0; while(i != s.size() && s(i) != c )++i; return i;}123456
7 w% U. N0 Z2 R若调用此函数find_char(“string”,’s’); 则出现编译错误,字符串和字符字面值是右值,可以通过const string &s 引用,此处字面值常量先隐式转化为一个临时const对象再初始化const string &s。, P4 o- m9 R1 ?; F {- Z! c h% V$ k$ G# U% Q( G9 U
区分int *matrix[10 ]; 和int (*matrix) [10];前者表示包含10个指针的指针数组,后者表示指向含有10个int型元素的数组的指针。数组下标优先级大于指针操作符。: N( d( K5 V$ D0 f7 s
5 ]0 U& o6 A* @3 t7 P& \' [0 w' F$ t
7 t3 } V- v Q0 gint main(int argc, char *argv[]) 中,argv是一个c风格的字符串数组,char *argv[]相当于char **argv,argc保存argv的字符串个数。6 E& L9 G, H* s
& @! y* Y& \, [左值可出现在赋值语句的右侧或左侧,而右值只能出现在赋值号的右侧。函数返回值用于初始化在调用函数处建立的临时对象。因此,函数可返回引用作为左值:const string &shorterString(const string &s1,const string &s2),形参和返回类型都是引用。但不要返回局部对象的引用,同样,可以返回指针,但不能返回局部对象的指针,否则会成为悬垂指针。& e. B Z' J' e" |$ H
递归函数是直接或间接调用自身的函数,必须要有中止条件,否则会无限循环。如:定义一个递归函数求取1×2×3……100的值:5 p! q/ G* g8 U. W0 Z4 I( t3 {* i2 G {' p& E8 V" d7 t7 T
- ( _2 J4 G3 x# V1 T9 L& L
o& `! Q$ M+ ^6 Z$ b( X; y/ i
. Y: A9 B6 T( @ t; G- P int f(int val){ if (val > 1) return f (val-1)*val; return val;}1234% g' E) K9 ?$ u1 G
函数声明可省略形参名,一般在声明阶段提供默认实参:string screenInit(string::size_type width = 80, string::size_type height = 20, char background = ‘c’); 当调用函数时,初始化值会从左向右覆盖默认实参值,因此应将最有可能变更的默认实参放在最左边。函数声明一般整理放在头文件中,在源文件中包含头文件。
' A9 o' u- w0 z) x' f& a函数中,每个名字都有作用域,每个对象都有生命期,形参和局部变量的生命期在函数调用过程中,它们的名字作用域限于函数块中从定义到快结束之间。若我们希望一个局部对象在函数调用结束后仍然具有生命,则可以定义静态局部变量:static关键字。% p$ T* y+ ~3 c
, }4 A! y t! x8 k f/ A
) Q9 y. K9 S# t% N4 K1 s: X' P定义inline内联函数是为了编译器在处理函数时按照函数块内语句展开,节省直接处理函数带来的花销。在普通函数前加inline关键字,并只能在头文件中定义。
0 m; J1 J7 P, \类的成员函数在类内声明,可以在类外定义,也可以在类内定义。其形参表包含一个隐式形参this指针,初始化为调用该成员函数的对象地址,如:在Sales_item类中定义的成员函数:$ _: z/ {+ B& `$ b! K) q* p. `* _
- f' S: i+ Y8 a8 T( c7 E
+ I' U7 e" a$ I/ o6 x) l' G+ V
6 a8 Q! [! p- N# Q+ A bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn;}123# j+ a4 w0 l2 G# i2 h' y
- s. y) p2 f% q, m9 ~6 j在调用total.same_isbn(trans)时,花括号前的const表明隐式指针this是一个指向total对象的const Sales_item* 类型的指针,该函数称为常量成员函数。return isbn == rhs.isbn相当于:
/ r% i9 x+ \) V' p" x. w( H" Q; _
return this->isbn == rhs.isbn; 函数体中可以显式地使用后者语句,但没有必要。7 { [/ ^ ~# b" C0 U& `6 ]* Y. L$ l! j& l# x% q1 u$ m
9 w2 G4 o" q; Y( T$ j8 o- I+ E$ u+ w8 X0 b) L0 Q% u/ `) {
构造函数是一种特殊的成员函数,用于初始化类,同名构造函数可以重载,由不同数目或类型的形参表区分。构造函数没有返回类型,和类同名。通常,我们将类放在与类同名的头文件中定义,而成员函数放在与类同名的源文件中定义。# a% E6 X2 ]+ M: p: ]2 ]
每个版本的重载函数应在同一个作用域中声明,局部同名函数会覆盖全局函数而不是重载。
$ K" l- m, o( N! t% }+ V) J% I5 O( a O9 [ F8 m
|
|