C++学习笔记2

1.const 与 #define的比较

C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:
1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只是在编译开始时进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应);
2) 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

2.typedef的作用

1)类型说明typedef
类型说明的格式为:typedef 类型 定义名;
类型说明只定义了一个数据类型的新名字而不是定义一种新的数据类型。定义名表示这个类型的新名字。
例如: 用下面语句定义整型的新名字:
typedef int SIGNED_INT;
使用说明后, SIGNED_INT就成为int的同义词了, 此时可以用SIGNED_INT 定
义整型变量。
例如: SIGNED_INT i, j;(与int i, j等效)。
但 long SIGNED_INT i, j; 是非法的。
typedef同样可用来说明结构、联合以及枚举和类。
说明一个结构的格式为:
typedef struct{
数据类型 成员名;
数据类型 成员名;

} 结构名;
此时可直接用结构名定义结构变量了。例如:

 typedef struct{
          char name[8];
          int class;
          char subclass[6];
          float math, phys, chem, engl, biol;
      } student;
      student Liuqi;

则Liuqi被定义为结构数组和结构指针。
注意:typedef定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。
2)可以用来定义与平台无关的类型,提高代码的可移植性
最近在代码里看到广为使用的typedef,起初以为typedef和define差不多,后来才发现前者比后者的功能更为强大。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
typedef long double REAL;
在不支持 long double 的平台二上,改为:
typedef double REAL;
在连 double 都不支持的平台三上,改为:
typedef float REAL;
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
3)为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。这点虽然理解,但在实际中自己用的较少,需要多看看。例如:
原声明:int (*a[5])(int, char);
变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int (*pFun)(int, char);
原声明的最简化版:
pFun a[5];
两种模式:type (*)(….)函数指针
type (*)[]数组指针
4)用typedef可以声明各种类型名,但不能用来定义变量;
5)typedef与#define有相似之处;如typedef int COUNT;和#define COUNT int;都是用“COUNT” 代替int。但二者的实质不同:#define在预编译时处理,它只作简单的字符串替换;而typedef则在编译时处理,并且不是作字符串替换,而是采用如同定义变量的方法来声明一个类型。
6)当不同的源文件中用到同一种数据类型(尤其是像数组、指针、结构体、共用体等类型)时,常用typedef声明一些数据类型,并把它们单独放在一个文件中,然后在需要用到这些类型数据的文件中用#include命令把它们包含进来。

3.内联函数、类成员函数、重载函数、指向函数的指针

1)内联函数
内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。内联扩展是用来消除函数调用时的时间开销。它通常用于频繁执行的函数。一个小内存空间的函数非常受益。如果没有内联函数,编译器可以决定哪些函数内联。程序员很少或没有控制哪些职能是内联的,哪些不是。 给这种控制程度,作用是程序员可以选择内联的特定应用 。
所以内联函数是典型的以空间换时间函数。同时在函数调用次数愈多的情况下其性能优势愈加明显。比普通函数耗时更少。
2)内联函数与预处理宏
内联函数的功能和预处理宏的功能相似。例如:#define MAX(x, y)x > y ? x : y
函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序执行完后,再返回到转去执行该函数的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间的消耗,影响效率,而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。但是,宏也有缺陷:
宏不能访问对象的私有成员;宏的定义很容易产生二意性。
3)内联函数必须是和函数体申明在一起,才有效。在C++中,在类的内部定义了函数体的函数,被默认为是内联函数。而不管你是否有inline关键字。内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我们类成员的数据了。对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成员函数定义成内联函数的话,将会获得比较好的效率。但是内联函数也有其局限性:函数中的执行代码不能太多,如果内联函数体过大,编译器就会把放弃内敛方式,而采用普通方式调用函数。这样内敛函数与普通函数无异。内联函数中不允许使用循环语句和开关语句。它只适合于不多于5行代码的函数。
4)类类型
每个类都定义了一个接口(interface)和一个实现(implementation)。接口由使用该类的代码需要执行的操作组成。实现一般包括该类所需要的数据,还有定义该类需要的但又不供一般性使用的函数。类定义时,在第一个访问标号前的任何成员都隐式指定为private,如果使用关键字struct,则这些成员都是public。这也是struct和class的区别,struct默认为public,而class默认为private型。类成员布局,一般顺序是创建函数,操纵函数,访问函数。数据成员一般都放在类定义的末尾。编译器隐式地将在类内定义的成员函数当作内联函数。每个成员函数都有一个额外的,隐含的形参this。在调用成员函数是,形参this初始化为调用函数的对象的地址。
const成员函数的引入:const所起的作用是改变隐含的this形参的类型,使其指向对象的const 对象类型的指针。const成员函数不能修改调用该函数的对象。
构造函数:
形参表为空:因为正在定义的构造函数是默认调用的,无需提供任何初值;
函数体为空:除了初始化数据成员外,没有其他工作可以做。
5)类中的数据成员与定义变量的区别:不能把类成员的初始化作为其定义的一部分,当定义数据成员时,只能指定该数据成员的名字和类型。类不是在类定义里定义数据成员时初始化数据成员,而是通过称为构造函数的特殊成员函数控制初始化。类的声明放置在头文件中,类的成员函数的定义放在与类同名的 .cpp文件中。内联函数可以放在头文件中定义,在类定义中就定义好了的成员函数默认为内联函数。
6)函数重载:
重载函数是指:在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。当然main函数是不能重载的,因为每个程序仅有一个main函数实例。
如果两个函数的声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。
如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的。(原因:函数不能仅仅基于不同的返回类型而实现重载。)
如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个重载函数都应在同一个作用域中声明。
函数匹配结果的三种可能性
a)编译器找到实参最佳匹配的函数,并生成调用该函数的代码;
b)找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将会给出编译错误信息;
c)存在多个与实参匹配的函数,但是没有一个是明显的最佳选择,这种情况也是错误的,该调用具有二义性。
7)指向重载函数的指针:
指针的类型必须与重载函数的一个版本精确匹配,如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误
eg: extern void ff( vector< double > );
extern void ff ( unsigned int );
a) void ( *pf1 )( unsigned int ) = &ff ; //ok
b) void ( *pf2 )( int ) = &ff; //error
c) double (*pf3 )( vector < double > );
pf3 = &ff; //error
8) 指向函数的指针:函数指针是指指向函数而非指向对象的指针,函数类型由其返回类型以及形参表确定,而与函数名无关。
用typedef简化函数指针的定义typedef bool ( *cmpFcn ) ( const string &, const string & ); cmpFcn 是一种指向函数的指针类型的名字。
指向函数的指针的初始化和赋值:
a)函数名可自动解释为指向函数的指针。
b)函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。
c)指向不同函数类型的指针之间不存在转换,将函数指针初始化为0,表示该指针不指向任何函数。
通过指针调用函数:指向函数的指针可用于调用它所指向的函数,可以不需要使用解引用操作符,直接通过指针调用函数。
函数指针形参:函数的形参可以是指向函数的指针。
void useBigger( const string &, const string &, bool (*)(const string &, const string & ) );
4.指针函数与函数指针
1)指针函数是指带指针的函数,本质上还是一个函数,函数返回类型是某一类型的指针。
类型标识符 *函数名(参数表)
int *f(x,y);
首先它是一个函数,只不过它的返回值是个地址。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。
表示:
Float *function();
float*p;
P = function(a);
注意:指针函数与函数指针的表示方法不同,不能混淆,最简单的区别方法就是看函数名前面的*号有没有被()包含,若被包含则为函数指针,否则是指针函数。
2)函数指针是指向函数的指针变量,即本质是一个指针变量。
int (f) (int x); / 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
指向函数的指针包含了函数的地址,可以通过它来调用函数,声明格式如下:
类型说明符 (*函数名)(参数)
这个函数名更应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明必须和它指向函数的声明保持一致。指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。
例如:
Void (*fptr)();
把函数的地址赋值给函数指针,可以采用下面两种格式:
Fptr = &function;
Fptr = function;
取地址运算符&不是必需的,因为单单一个函数标识符就标号表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
可以采用如下两种方式来通过指针调用函数:
X = (*fptr)();
X = fptr();
第二种格式看上去和函数调用无异。但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值