一、C++名字空间详解
C++ using namespace std 详解
所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
(一)<iostream>和<iostream.h>是不一样,前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。
后缀为.h的头文件c++标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。
因此,当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespacestd;这样才能正确使用cout。
(二)所谓namespace,是指标识符的各种可见范围。
C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:
1、直接指定标识符。例如std::ostream而不是ostream。完整语句如下:
std::cout << std::hex<< 3.4<< std::endl;
2、使用using关键字声明要使用的符号(一般是类名)。【这种方式在STL书中称为using declaration方式,即使用声明标识符的方式,只有声明后,就可以用cout代替std::cout】
using std::cout;
using std::endl;
以上程序可以写成
cout << std::hex<< 3.4<< endl;
3、最方便的就是使用using namespace std;
例如:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句可以如下写:
cout << hex<< 3.4<< endl;
因为标准库非常的庞大,所程序员在选择的类的名称或函数名时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。
命名空间std封装的是标准程序库的名称,标准程序库为了和以前的头文件区别,一般不加".h"
(三)using namespace std 的用法举例:
因为标准库非常的庞大,所程序员在选择的类的名称或函数名时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。
---------------------------------------------------------------
名字空间,实质上也是为了方便程序在不同平台上正确的运行。
---------------------------------------------------------------
尽量不要使用using namespace std;
今天用了VISUAL C++写了个小程序(VS2005),很简单很简单的,但是就是编译不通过
出现一个奇怪的问题:错误 1 error C2668: “max”: 对重载函数的调用不明确
最初代码如下
#include <iostream>
using namespace std;
template <typename T>
T max (T a,T b)
{
return ((a>b)?a:b);
}
void main()
{
double x,y;
cin>>x>>y;
cout<<"Max number is"<<(max(x,y))<<endl;
cin>>x;
}
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
template <typename T>
T max (T a,T b)
{
return ((a>b)?a:b);
}
int main()
{
double x,y;
cin>>x>>y;
cout<<"Max number is"<<(max(x,y))<<endl;
cin>>x;
}
流。另外,输入输出流的C++标准规范接口在一些微妙的细节上都已改进,因此,<iostream>和<iostream.h>在接口和执行上都是不同的。最后,<iostream>的各组
成都是以STL的形式声明的,然而<iostream.h>的各组成都是声明成全局型的。因为这些实质上的不同,你不能在一个程序中混淆使用这两个库。做为一种习
惯,在新的代码中一般使用<iostream>,但如果你处理的是过去编写的代码,为了继承可以用继续用<iostream.h>旧保持代码的一致性。
二、关键字typename
template<class T>
class MyClass{
typename T::SubType * ptr;
..........
};
上面代码中的typename关键字不可以省略,否则T::SubType * ptr语句中的“*”被解释为乘号,即两个变量相乘。另外,上面代码中的T类型有个必要条件,必须是形如下面的类Q,含有类型成员SubType。
class Q{
typedef int SubType; //此句作用是Q类中定义了一个成员,这个成员是个类型。
...............
};
之所以出现上面现象是因为:C++的一个一般规则是,除了以typenme修饰的符合外,template内的任何标志符号都被视为一个值(value)而非一个类型。
三、C++中的类拷贝构造函数和模板拷贝构造函数
类(包括模板类)构造函数是真实的构造函数;然而模板构造函数(包括拷贝构造函数),其实只是模板函数。两者不能混为一谈。在一个模板类中,构造函数和模板构造函数同时存在时,优先调用构造函数(包括拷贝构造函数)。只有当确切符合模板构造函数的接口时,才调用模板构造函数。编译器永远不会把模板构造函数视为构造函数,即使客户没有自己定义拷贝构造函数,编译器也会生成一个默认的拷贝构造函数,这种情况同样存在于拷贝赋值函数和模板拷贝赋值函数。请看下面的例子:
#include <iostream>
using namespace std;
template <typename T>
class TempClass{
public:
T d;
//两个构造函数,其中第二个是拷贝构造函数
TempClass<T>(T _d=0):d(_d){cout<<"This is TempClass Constructor1. "<<endl;};
TempClass<T>(TempClass<T> &_tmp):d(_tmp.d){cout<<"This is TempClass Constructor2. "<<endl;};
template <typename O> //模板构造函数
TempClass<T>(TempClass<O> &_tmp):d(_tmp.d){cout<<"This is a template constructor, not a TempClass Constructor."<<endl;};
};
int main()
{
TempClass<int> a;
TempClass<int> b(a); //调用拷贝构造函数,即使在该模板类中用户没有自定义该函数,编译器也会生成一个默认拷贝构造函数。因为编译器永远不会认为一个模板构造函数是一个构造函数
TempClass<double> c(a); //调用模板拷贝构造函数
return 0;
}
输出结果是:
>This is TempClass Constructor1.
>This is TempClass Constructor2.
>This is a template constructor, not a TempClass Constructor.
四、c++易忘的书写规范(分号的问题)
类的定义(实现),在右大括号后面必须加分号“;”。
函数的定义(实现),在右大括号后面不能加分号。
所以常见到头文件中各个类的定义都是以分号隔开,而cpp文件中各个函数实现之间没有分号。
五、深入理解迭代器
MyArray类----------ArrayIterator类
MyLink类------------LinkIterator类
其中MyArray是一个数组的包装,MyLink是一个链表的包装,都是一个容器或集合。
从上面可以看出不同的容器或集合对应不同的独立的迭代器类。
所以说迭代器类和对应的容器类是两个独立并列关系的类,本质上不存在录属关系,只不过有时候为了体现特定的容器应该对应特定的迭代器,所以常常把迭代器类的定义放到容器类的定义中,感觉上好像这个迭代器录属于对应的容器一样。当分析问题时一定要把两个类看成并列关系。
可以把迭代器就理解成一个指针的包装,其内部存放有一个指向容器元素的指针。
对于容器类,至少要有:
(1)T * Begin()函数返回起始迭代指针
(2)T * End()函数返回结束迭代指针。注意这里的指针是指向容器内部的某一个元素。
对于迭代器类,至少要有:
(1)保存住一个指向元素的指针,并实现一个以元素指针为形参的构造函数,以便通过构造函数给保存的那个指向元素的指针赋值。
(2)不相等判断(重载!=运算符),以便判断两个迭代器是否相等(即是否指向同一个元素);
(3)++运算(重载++运算符)或实现next()函数,以便让迭代器指向容器中的下一个元素;
(4)*运算(重载*运算符)或实现get()函数,以便返回迭代器当前指向的元素。
下面实现一个容器和迭代器,并举例说明简单使用方法:
#include "stdafx.h"
#include <iostream>
using namespace std;
template<class T>
class MyLink
{
public:
struct Unit //链表结点,某个元素
{
T value;
Unit * next;
};//end-struct
class LinkIterator //迭代器,虽放在容器内部,但和容器是并列独立关系
{
Unit * init;
public:
LinkIterator(Unit * init)
{
this->init=init;
}
bool operator !=(LinkIterator& it)
{
return this->init!=it.init;
}
void operator ++(int)
{
init=init->next;
}
Unit operator *()
{
return *init;
}
};//end-class
Unit * head; //链表头
Unit * tail; //链表尾
Unit * prev;
public:
MyLink()
{
head=tail=prev=NULL;
}
void Add(T &value)
{
Unit *u=new Unit();
u->value=value;
u->next=NULL;
if(head==NULL)
{
head=u;
prev=u;
}
else
{
prev->next=u;
prev=u;
}
tail=u->next;
}
Unit * Begin()
{
return head;
}
Unit * End()
{
return tail;
}
virtual ~MyLink()
{
if(head!=NULL)
{
Unit *prev=head;
Unit *next=NULL;
while(prev!=tail)
{
next=prev->next;
delete prev;
prev=next;
}
}
}
};
//template<typename T> //重载全局的"<<"运算符,以便输出MyLink容器中的某个元素
ostream& operator <<(ostream& os, /*MyLink<T>::Unit & s*/ MyLink<int>::Unit & s) //不知道为什么使用模版时会出错,直接用int就可以编译通过
{
os<<s.value;
return os;
}
template<typename T>
void display(T start,T end) //依次显示容器中元素
{
cout<<endl;
for(T mid=start;mid!=end;mid++)
{
cout<<*mid<<"\t";
/*这里可能有疑问?为什么编译器知道<<运算符右操作数是一个T类型就可以呢?
其实函数模版根本不参与编译,只有用到了这个函数,才生成一个对应的具体函数然后进行编译。
我测试过,随便写一个函数模版,在这个函数模版中写一句明显编译错误的语句,
只要不使用到这个函数模版,则编译可以通过,不会报错。*/
}
cout<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
int m=0;
MyLink<int>ml; //ml是个容器
for(int i=0;i<5;i++)
{
m=i+1;
ml.Add(m);
}
MyLink<int>::LinkIterator start=ml.Begin();
MyLink<int>::LinkIterator end=ml.End();
/*对上面两句进行下分析:ml.Begin()返回值类型是Unit类型,那么怎么会把Unit类型的对象直接赋给LinkIterator类型的对象了呢?
原来这里用到了c++中的“单参构造函数都有隐式的类型转换”,只要传递的值是构造函数参数需要的类型,就可以隐式转换,从而生成一个临时对象,
再把这个临时对象赋给start*/
display(start,end);
system("PAUSE");
return 0;
}
输出结果:
六、函数对象和函数适配器
(1)函数对象
把函数作为对象是程序设计的新思维。
一个函数对象是一个封装了一个特定函数的类的实例,这个类必须重载ooperator()即括号运算符(称为函数调用运算符)。当然使用时和函数调用的样子一样,只不过此时的函数名不是函数名而是一个对象名。
具体使用参加如下代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
class Complex
{
public:
float real;
float virt;
public:
Complex()
{
this->real=0.0f;
this->virt=0.0f;
}
Complex(float real,float virt)
{
this->real=real;
this->virt=virt;
}
Complex operator+(const Complex& c) const
{
Complex v;
v.real=real+c.real;
v.virt=virt+c.virt;
return v;
}
};
/*
template<class Arg1,class Arg2,class Result>
struct binary_function{ //binary_function的原型
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type; //即使Arg1和Arg2一模一样也可以,不影响typedef,也就是说比如一个int可以有多个别名
typedef Result result_type;
};
*/
template<class T> //binary_function是std定义好了的模版类,其原型见上面注释掉的东西
struct plus:public binary_function<T,T,T> //发现三个模版参数一样也行,仔细体会一下
{
result_type operator()(const first_argument_type& x,const second_argument_type& y) const
{
return x+y;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Complex c1=Complex(1.0f,2.0f);//等价于Complex c1(1.0f,2.0f); 其实构造函数调用前不加new就是返回一个临时对象,加new则返回临时对象的地址。
Complex c2(3.0f,4.0f);
Complex c3=c1+c2;
plus<Complex> funObj; //实例化一个函数对象
Complex c4=funObj(c1,c2);
//或直接写Complex c4=plus<Complex>()(c1,c2);
//此时plus<Complex>()整体返回一个临时plus类的对象
cout<<c3.real<<"+"<<c3.virt<<"i"<<endl;
cout<<c4.real<<"+"<<c4.virt<<"i"<<endl;
system("PAUSE");
return 0;
}
输出结果:4+6i
4+6i
(2)函数适配器
首先,这个适配器本质是个函数,其作用是把普通函数适配成(包装成)一个对象(这些对象的类型都是stl中预先定义好的比如一元函数等)。也就是说这个适配器函数的返回值是一个对象,其形参部分与普通函数相关,从而把普通函数包装成了一个stl中定义好类型的对象,以便用于stl中已经定义好了的各种算法中。
下面代码功能是,添加学生信息对象向量,并调用Student类中的Show函数,屏幕上显示学生的信息:
#include <functional> //帮助构建的函数对象,里面包含<xfunctional>从而包含有如一元函数的定义等
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Student
{
string strNO; //学号
string strName; //姓名
public:
Student(string strNO,string strName):strNO(strNO),strName(strName){}
bool show() //学生信息显示函数
{
cout<<strNO<<":"<<strName<<endl;
return true;
}
};
void main()
{
//测试用适配器mem_fun_ref
Student s1("1001","zhangsan");
Student s2("1002","lisi");
vector<Student> v;
v.push_back(s1);
v.push_back(s2);
for_each(v.begin(),v.end(),mem_fun_ref(&Student::show));//把成员函数包装成一个函数对象
/*说明,mem_fun_ref这个函数模版形参是一个函数指针类型,通过这个模版函数的调用就完成了
把类的普通成员函数包装成了一个函数对象,且这个函数对象是unary_function(一元函数模版类)
类型的对象*/
//有的版本的c++也可以如下
//for_each(v.begin(),v.end(),mem_fun_ref(Student::show));
//测试用适配器mem_fun
Student * ps1=new Student("1003","wangwu");
Student * ps2=new Student("1004","zhaoliu");
vector<Student *>pv;
pv.push_back(ps1);
pv.push_back(ps2);
for_each(pv.begin(),pv.end(),mem_fun(&Student::show));
/*最后说明下mem_fun_ref和mem_fun的区别:如果for_each操作的集合(通过迭代器)
是基于对象实体的,则用mem_fun_ref;如果集合是基于指针的,则用mem_fun*/
system("pause");
//从程序中调用系统命令(和shell命令),暂停并输出“Press any key to exit”
}
输出结果:1001:zhangsan
1002:lisi
1003:wangwu
1004:zhaoliu
七、通用容器
容器分类:
(1)序列式容器,主要包括:vector,deque和list
(2)关联式容器,主要包括:set、multiset、map和multimap
(3)容器适配器,主要包括:stack和queue。容器适配器是对已有的容器进行再封装,不是一个真正的新容器,但把它归为一类。
所有容器的共有属性和方法主要有:empty,size,begin(),end(),erase(),clear()。
(1)vector容器
vector就是动态数组。vector一定是连续的一片存储空间。主要的函数有:push_back(),pop_back(),front(),back()。
(2)deque容器
deque是双端队列。deque是小片连续,小片间用链表相连。主要函数有:push_front(),push_back(),pop_front(),pop_back(),front(),back()。
(3)list容器
list就是个双向链表。主要函数有:push_back(),push_front(),pop_back(),pop_front(),front(),back()。
(4)队列和堆栈
队列和堆栈一般是对上面三种容器的封装,所以称为容器适配器。所以用于生成它们的类模版具有两个参数形如:template (class T, class Container=deque<T>)。也就是说类模版第一个参数指定容器中元素的类型,第二个参数指定用什么基础容器存放元素,这些基础容器一般也就是上述三种容器,另外queue和stack类模版第二个参数默认值都是双端队列deque。
队列和堆栈常用的函数有:
void push(const T& t):把t元素压入队尾(栈顶)。
void pop():当队列(栈)非空情况下,删除队头(栈顶)元素。
另外,栈独有的函数:T& top():当栈非空情况下,返回栈顶元素的引用。
队列独有的函数:T& front():当队列非空情况下,返回队头元素的引用; T& back():当队列非空情况下,返回队尾元素的引用。