1
用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读 性。 C++中是用关键字typedef定义一个标识符来代表一种数据类型,该标识符可以象其它基本类型的标识符一样使用。在用typedef进行类型定 义时,其语法和变量定义很相似。typedef定义的一般形式为: typedef 原类型名 新类型名 例如: typedef int * intptr; 定义intptr为一个指向整型的指针类型。变量定义语句: intptr p; 等价于: int * p; 需要 注意 的是:typedef定义的类型只是C++已有类型的别名,而不是新类型。有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。
2
namespace的使用
有名的命名空间:
namespace 命名空间名 {
声明序列可选
}
无名的命名空间:
namespace {
声明序列可选
}
-------------------------------------------------
命名空间的成员,是在命名空间定义中的花括号内声明了名称。可以在命名空间的定义内,定义命名空间 的成员(内部定义)。也可以只在命名空间的定义内声明成员,而在命名空间的定义之外,定义命名空间的成员(外部定义)。
命名空间成员的外部定义的格式为:
命名空间名::成员名 ……
例如:
// out.h
namespace Outer { // 命名空间Outer的定义
int i; // 命名空间Outer的成员i的内部定义
namespace Inner { // 子命名空间Inner的内部定义
void f() { i++; } // 命名空间Inner的成员f()的内部定义,其中的i为Outer::i
int i;
void g() { i++; } // 命名空间Inner的成员g()的内部定义,其中的i为Inner::i
void h(); // 命名空间Inner的成员h()的声明
}
void f(); // 命名空间Outer的成员f()的声明
// namespace Inner2; // 错误,不能声明子命名空间
}
void Outer::f() {i--;} // 命名空间Outer的成员f()的外部定义
void Outer::Inner::h() {i--;} // 命名空间Inner的成员h()的外部定义
// namespace Outer::Inner2 {} // 错误,不能在外部定义子命名空间
注意:
不能在命名空间的定义中声明(另一个嵌套的)子命名空间,只能在命名空间的定义中定义子命名空间。
也不能直接使用“命名空间名::成员名 ……”定义方式,为命名空间添加新成员,而必须先在命名空间的定义中添加新成员的声明。另外,命名空间是开放的,即可以随时把新的成员名称加入到已有的命 名空间之中去。方法是,多次声明和 定义同一命名空间,每次添加自己的新成员和名称。例如:
namespace A {
int i;搞怪小四
void f();
} // 现在A有成员i和f()
namespace A {
int j;
void g();
} // 现在A有成员i、f()、j和g()
还可以用多种方法,来组合现有的命名空间,让它们为我所用。例如:
namespace My_lib {
using namespace His_string;
using namespace Her_vector;
using Your_list::List;
void my_f(String &, List &);
}
……
using namespace My_lib;
……
Vector<String> vs[5];
List<int> li[10];
1
my_f(vs[2], li[5]);
1
使用命名空间
作用域解析运算符(::)
3
1)虚函数和纯虚函数
(1). 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
( 2). 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)
只有声明而没有定义。
(3). 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
( 4). 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
(5). 虚函数的定义形式:virtual {method body} 纯虚函数的定义形式:virtual { } = 0;
在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。 (6). 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。
纯虚函数和虚函数的区别在于前者不包含定义,而后者包含函数体。
2)抽象类:
抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
⑴抽象类的定义:
称带有纯虚函数的类为抽象类。
⑵抽象类的作用:
抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
(3)使用抽象类时注意:
? 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
? 抽象类是不能定义对象的。
4.
调用操作符重载:我们可以为类类型的对象重载调用操作符即小括弧”()“,定义了调用操作符的类,在使用定义的调用操作符时,行为就像是调用函数,所以这种对象称为函数对象,即行为类似函数的对象:
如下例子为类A定义了调用操作符,功能是返回一个绝对值:
class A
{
public:
int operator() (int val)
{
return val < 0 ? -val : val;
}
};
调用操作符使用以下两种方法都可以:
A a;
int n = a.operator()(-1);
int n = a(-1); //使用方式像是调用了一个函数
调用操作符可以重载多个版本,通过函数形参的类型及数目区分,如再为类A重载一个形参类型为char *的调用操作符函数:
void operator(char *p)
{
*p = '\0';
}
使用以上调用操作符:
char arr[] = "ABC";
char *pa = arr;
a.operator()(pa);
a(pa);
二:函数对象用于标准库算法:
a:例如查找比指定值大的数,可以先定义一个类B,为其定义调用操作符,使用该类的对象实现查找:
class B
{
public:
B(int val = 0) : ival(val) {} //构造函数初始化成员 ival;
bool operator()(const int &v)
{
return v > ival; //返回真值,如果传递进来的实参大于成员ival,ival在调用构造函数时被初始化;
}
private:
int ival;
};
用标准库算法find_if进行查找,比如查找大于10的数:
int b[] = {1, 52, 12, 30, 9, 19};
vector<int> ivec(b, b+6);
vector<int>::iterator ite = ivec.begin();
while((ite = find_if(ite, ivec.end(), B(10))) != ivec.end()) { cout << *ite << " "; ite++; } find_if函数的第三个参数是一个接受一个参数并返回bool型的函数,在这里我们可以传递给find_if算法函数一个类类型B的临时对象,通过传 递值10给构造函数初始成员ival,现在find_if每次调用它的函数形参时,依次将vector中的每个元素都应用类B的调用操作符,然后该操作符 根据成员ival的值,对传进的实参即vector的每个元素进行比较,如果函数对象返回为真,则find_if返回指向该元素的迭代器;
b:统计大于指定值的次数,以上述代码为基础:
int n = count_if(ivec.begin(), ivec.end(), B(10));
同样,count_if以函数调用的方式调用类B调用操作符,对vector中每个元素进行比较,如果为真,则计数器加1,知道vector的末端元素;
全部代码:
- #include <iostream>
- #include <vector>
- #include <algorithm>
- using namespace std;
- class A
- {
- public:
- int operator() (int val)
- {
- return val > 0 ? val : -val;
- }
- void operator() (char *p)
- {
- *p = 'B';
- }
- };
- class B
- {
- public:
- B(int val = 0) : ival(val) {} //构造函数初始化成员 ival;
- bool operator()(const int &v)
- {
- return v > ival; //返回真值,如果传递进来的实参大于成员ival,ival在调用构造函数时被初始化;
- }
- private:
- int ival;
- };
- int main()
- {
- int i = -42;
- A a;
- unsigned int ui = a(i);
- // unsigned int ui = a.operator()(i);
- cout << ui << endl;
- char arr[] = "ABC";
- char *pa = arr;
- a(pa);
- //a.operator()(pa);
- cout << pa << endl;
- int b[] = {1, 52, 12, 30, 9, 19};
- vector<int> ivec(b, b+6);
- vector<int>::iterator ite = ivec.begin();
- while((ite = find_if(ite, ivec.end(), B(10))) != ivec.end())
- {
- cout << *ite << " ";
- ite++;
- }
- cout << endl;
- int n = count_if(ivec.begin(), ivec.end(), B(10));
- cout << "n:" << n << endl;
- return 0;
- }
运行结果:
42
BBC 52 12 30 19 n:4 上述代码g++编译通过!
函数调用操作符是(),因此,此操作符的函数重载是operator()()。重载函数调用操作符的类对象称为函数对象或仿函数(functor),因为我们可以像使用函数名一样使用对象名。先来看一个简单的例子。下面是重载了函数调用操作符的一个类:
- class Area
- {
- public:
- int operator()(int length, int width) { return length*width; }
- };
此类中的操作符函数计算一个面积,它是两个整数实参的乘积。为了使用此操作符函数,只需要创建一个类型为Area的对象,例如:
- Area area; // Create function object
- int pitchLength(100), pitchWidth(50);
- int pitchArea = area(pitchLength, pitchWidth); // Execute function call overload
第一条语句创建第三条语句中使用的area对象,第三条语句使用此对象来调用对象的函数调用操作符。在此例中,返回的是足球场的面积。
当然,也可以将一个函数对象传递给另一个函数,就像传递任何其他对象一样。看看下面这个函数:
- void printArea(int length, int width, Area& area)
- {
- cout << "Area is " << area(length, width);
- }
下面是使用此函数的语句:
- printArea(20, 35, Area());
这条语句调用printArea()函数,前两个实参分别指定矩形的长和宽。第三个实参调用默认构造函数,以创建一个Area对象,函数中计算面积 时要使用此Area对象。因此,函数对象提供了一种方式,可以将函数作为实参传递给另一个函数。与使用函数指针相比,这种方式既简单又容易。
注意:第10章将学习std::function<>模板,它为传递函数提供了更大的灵活性。