弄死C++
1.传值与传引用与传指针???
使用返回:
传引用:
局限性:
!、任何时候都不能使用指向空值的引用,也就是引用应该被初始化。
一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,那么,请一定记住:使用指针吧。
某位程序员这么写:
int* myPoint=0;
int &myCaller=*myPoint; //可以吗???这种恐怖的写法。。。。结果的不确定性让你无法左右你的程序,请务必小心这种写法
优点:
!、因为引用必须被初始化,才能使用,这也意味着:使用引用,你不必去检查其合法性,故:引用的效率比指针高。
所以:引用像老婆一样。。一定终身。。指针像小蜜一样,随人而变。
为什么重载操作符时需要传引用:仅为了避免不需要的语义误解!比如你重载了[]符号,那么你返回了一个 mya[2]=10,如果返回的是一个指针呢? *mya[2]这样的表达会让你误解:这是一个向量指针。
三者比较:
1)“传值”需要对象的构造和析构,可能会很耗时。
2)“传值”对于一般对象而言,传递的大小总是大于“传引用”
3) 对于小对象,例如int,“传值”会比“传引用”更高效。
二、名字查找规则、覆盖、隐藏。。。。。
名字查找规则:
当你调用一个函数时,编译器会智能的分析哪个调用的可能性最大,进行搜索加载
Class Father
{
public virtual Codding(int);
};
Class Child: public Father
{
public Codding(void*); //空指针,说明它可以是任何类型
}
void main()
{
Child ch=new Child();
ch.Codding(10);
}
结果是什么:没有找到!!!,程序出错了。。。
为什么呢,因为 名字查找规则的匹配算法在当前范围中查找匹配的函数(也就是Child),找到了一个名字匹配的函数,它不搜索了,同时它发现:参数不一样。所以匹配不了。。。
如果一些编译器给出了“子类的成员函数隐藏了基类的函数”,这就说明了你不应在子类声明名字和基类一样的函数。否则,你不是继承。而是隐藏它了。(除非它们都是虚函数且拥有自己的签名)
那么。。。什么是虚函数呢?简单的说就是通过虚函数,基类可以可以访问子类函数,那么如何访问呢?通过动态练遍的方式。eg:
1.简介
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次:
class A
{
public:
virtual void foo() { cout << "A::foo() is called" << endl;}
};
class B: public A
{
public:
virtual void foo() { cout << "B::foo() is called" << endl;}
};
那么,在使用的时候,我们可以:
A * a = new B();
a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
===就是因为父类不知道调用的是哪个函数,所以,它才是“虚的”!!。虚函数智能借助指针和引用来实现多态的效果。
那什么事多态呢??
我们假定有这个调用:
void FunUsed(A *a)
{
a->foo();//不知道调用的是哪个函数,具体调用的是哪个函数,要根据你实现传入的是哪个实例来判断。所以。。是多态!!!它会变幻。。。
}
---多态有什么意义。。简单的抽象的说:是减少耦合度。用户不需要你的类结构,但是他就是可以使用你的函数。
如何实现联编呢?
一种典型的做法是通过这样一个方式:虚连接表
class A
{
public:
virtual void foo();
};
class B: public A
{
public:
void foo(); // 没有virtual关键字!
};
class C: public B // 从B继承,不是从A继承!
{
public:
void foo(); // 也没有virtual关键字!
};
这种情况下,B::foo()是虚函数,C::foo()也同样是虚函数。因此,可以说,基类声明的虚函数,在派生类中也是虚函数,即使不再使用virtual关键字。
但是一个类的虚函数被其构造函数或者析构函数调用的时候,他们就变成普通函数了。也就是说在这两种函数中,自己就无法多态了。。、
class A
{
A()
{football();}
~A();
virtual void football():
};
class B
{
virtual void football();
}
void main()
{
A* myA=new B(); //无论如何调用的都是 A.football();
}
在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现。通过这样的方法,就可以将对象的行为抽象化。
以设计模式[2]中Factory Method模式为例,Creator的factoryMethod()就是虚函数,派生类override这个函数后,产生不同的Product类,被产生的Product类被基类的AnOperation()函数使用。基类的AnOperation()函数针对Product类进行操作,当然Product类一定也有多态(虚函数)。
另外一个例子就是集合操作,假设你有一个以A类为基类的类层次,又用了一个std::vector<A *>来保存这个类层次中不同类的实例指针,那么你一定希望在对这个集合中的类进行操作的时候,不要把每个指针再cast回到它原来的类型(派生类),而是希望对他们进行同样的操作。那么就应该将这个“一样的操作”声明为virtual。
现实中,远不只我举的这两个例子,但是大的原则都是我前面说到的“如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的”。这句话也可以反过来说:“如果你发现基类提供了虚函数,那么你最好override它”。
隐藏只有两点:
当父类与子类的函数名、且参数一样并且没有virtual 修饰,那么父类的函数被隐藏。(废话,这个就是你子类的函数,可以找到且参数一样,何必去父类那再寻找)
当父类和子类的函数名一样,但是参数不一样,有virtual修饰,那么父类被隐藏(名字查找规则)。
函数指针:
函数指针的声明:
int (*pFun)(int temp); //定义了一个指向函数的指针
如何应用:
code:
int printInt(int i)
{
cout<<"the number is "<<i<<endl;
}
void main()
{
int (*myPrintInt)(int temp);
(*myPrintInt)=printInt; //赋值, printInt就是首地址,这和数组类似
cout<<"输出结果,用C++的方式"<<myprintInt(5)<<endl;
cout<<"输出结果,用C的方式"<<(*myprintInt)(3)<<endl;
但是,一般情况下,是使用typedef 来定义函数指针。
如下:
如上面那边我们会这么写:
typedef int (*myPrintInt)(int temp); //这部分就定义了一个函数指针,类型名为 myPrintInt
myPrintInt myPrintInt1; //这里就定义了一个myPrintInt1的函数指针
-----所以通过这种方式,在定义多个的函数指针的时候,效果好很多。。。。
}
~~~~(>_<)~~~~ 。。加了那么多。。一个浏览器死掉。。
刚刚写了一些typedef 来判断结构体是否含有某个函数的方式。。。算了。。
接下去写:
STL. 摘自:http://www.stlchina.org/twiki/bin/view.pl/Main/STLPracticalGuide 的一个程序
always wanted to write one and here is my golden 24 karet opportunity: a hello world program. 这个程序把一个字符串转换为一个字符vector,然后以逐个字符显示整个字符串。vector就像是盛放变长数组的花园,在STL所有容器中,大约有一半是基于vector的,故可以这么说,尚若你掌握了这个程序,那么你就理解了整个STL的一半了
// Program: Vector Demo 1
// Purpose: 用于演示STL vector
// #include "stdafx.h" - 如果你使用预编译需要包含此文件[[#ExplainIn2][注2]]
#include <vector> // STL vector 头文件. 注意,并没有".h"
#include <iostream> // 需要用到 cout
using namespace std; // 确保命名空间是 std
char* szHW = "Hello World";
// 众所周知,这是个以NULL结尾的字符数组
int main(int argc, char* argv[])
{
vector <char> vec; // 一个字符类型的vector(相当于STL中的数组)
// 为字符vector定义迭代器
vector <char>::iterator vi;
// 初始化字符vector,循环整个字符串,把每个字符放入vector中,直至字符串末尾的NULL字符
char* cptr = szHW; // Hello World 字符串的首地址
while (*cptr != '\0')
{ vec.push_back(*cptr); cptr++; }
// push_back 函数把数据插入vector的最后
// 把存在STL数组中的每个字符打印到屏幕上
for (vi=vec.begin(); vi!=vec.end(); vi++)
// 这就是在STL中循环的标准判断方式- 经常使用 "!=" 而不是 "<"
// 某些容器可能并没有重载操作符 "<" 。
//begin()和end()会得到vector的开头和结尾两个元素的迭代器(指针)
{ cout << *vi; } // 使用间接操作符(*)从迭代器中取得数据
cout << endl; // 输出完毕,打印 "\n"
return 0;
}
push_back 是用来向vector或deque容器中插入数据的标准函数。insert是类似功能的函数,适用于所有容器,但用法更复杂。end()实际上表示在最后的位置再加一,以便循环可以正常执行 - 它返回的指针指向最靠近数组界限的数据。就像普通循环中的数组,比如for (i=0; i<6; i++) {ar[i] = i;} ——ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。
STL的排序
http://www.stlchina.org/twiki/bin/view.pl/Main/STLSortAlgorithms
如果你需要自己定义比较函数,你可以把你定义好的仿函数(functor)作为参数传入。每种算法都支持传入比较函数。以下是所有STL sort算法函数的名字列表:
函数名 | 功能描述 |
sort | 对给定区间所有元素进行排序 |
stable_sort | 对给定区间所有元素进行稳定排序 |
partial_sort | 对给定区间所有元素部分排序 |
partial_sort_copy | 对给定区间复制并排序 |
nth_element | 找出给定区间的某个位置对应的元素 |
is_sorted | 判断一个区间是否已经排好序 |
partition | 使得符合某个条件的元素放在前面 |
stable_partition | 相对稳定的使得符合某个条件的元素放在前面 |
1.2 sort 中的比较函数
当你需要按照某种特定方式进行排序时,你需要给sort指定比较函数,否则程序会自动提供给你一个比较函数。
vector < > vect;
sort(vect.begin(), vect.end());
sort(vect.begin(), vect.end(), less<>() );
上述例子中系统自己为sort提供了less仿函数。在STL中还提供了其他仿函数,以下是仿函数列表:
名称 | 功能描述 |
equal_to | 相等 |
not_equal_to | 不相等 |
less | 小于 |
greater | 大于 |
less_equal | 小于等于 |
greater_equal | 大于等于 |
需要注意的是,这些函数不是都能适用于你的sort算法,如何选择,决定于你的应用。另外,不能直接写入仿函数的名字,而是要写其重载的()函数:
less<int>()
greater<int>()
当你的容器中元素时一些标准类型(int float char)或者string时,你可以直接使用这些函数模板。但如果你时自己定义的类型或者你需要按照其他方式排序,你可以有两种方法来达到效果:一种是自己写比较函数。另一种是重载类型的'<'操作赋。
再来说一点:
C++的内联函数
为什么需要内联函数:因为我们在调用、执行函数的时候,其实是一个奔波的过程,函数的调用过程是将函数要执行的顺序保存到一张表中,函数执行完后,再转去执行其执行前的函数,这要求保护现场并记忆执行的地址,对于大型的程序,这势必会影响效率
内联函数的定义:
inline int add(int x, int y)
{
return x+y;
}
他是在编译的时候被替代,而不是在运行的时候被调用。
内联定义的规则:
1. 类内为内联函数,类外委非内联函数,(所以要求短函数在类内,长函数在类外)---具体的说应该是在类声明的文件中定义的函数都被弄成内联函数。如果是类外的,一样要加上inline
----例如:
我们在类中的变量一般声明为私有,但是外部读取,那么我们需要的就是提供给调用的接口,这些接口设计成内联的会更好。
2.可以为类外定义的函数指定 inline 关键字,强行为内联函数。
3.在内联函数内不允许用循环语句和开关语句。
4.内联函数的定义必须出现在内联函数第一次被调用之前。