C++

   

pair是一个模板数据类型,其中包含两个数据值,两个数据值可以不同

如 pair<int,string>a(2,"fgh");则a是一个pair类型,它包括两个数据,第一个数据是int型2,第二个数据是string型"fgh"。

   由于pair类型的使用比较繁琐,因为如果要定义多个形同的pair类型的时候,可以时候typedef简化声明:

typedef pair<string, string> author;

author pro("May", "Lily");

author joye("James", "Joyce");

对pair对象的操作

  • 对于pair类,由于它只有两个元素,分别名为first和second,因此直接使用普通的点操作符即可访问其成员

     pair<string, string> a("Lily", "Poly"); 

     string name;

     name = pair.second;

在使用map的插入功能时,可以这样来写:

        map<string,int> m;

        m.insert(pair<string,int>("Jake",3));

    在解题时候,通常有遇到设置为无穷大的情况。这时候通常用0x7fffffff来设置,他是计算机32位整数最大数,相当于INT_MAX.但是在很多时候这样设置并不会是最佳的,还可能导致bug,这是由于我们有的时候希望无穷大+无穷大=无穷大,比如在prim算法或者Dijstra算法中对边的松弛操作,这个时候INT_MAX随便加上一个数就会溢出,从而导致结果错误。

    事实上另外一个数字0x3f3f3f3f的十进制是1061109567,这与0x7fffffff是同一个数量级的。我们用0x3f3f3f来代替0x7fffffff可以满足无穷大加无穷大依然是无穷大的条件,这样可以避免灾难性的错误。另外0x3f3f3f还可以使用memset函数批量赋值,例如要将数组dis[]设置为无穷大:              

1

memset(dis,0x3f,sizeof(dis));

如果数组dis是long long 型,则上面语句将dis设置为4557430888798830399,若dis为int型 ,则上面语句将dis设置为1061109567无论在long long还是int,两个无穷的和都不会爆。上面的0x3f是一个字节0x3f3f3f3f一共有四个这样的字节。一般情况下,0x3f3f3f3f是一个设置无穷的不错选择。

处理类型(typedef,uisng,auto,decltype)

一:类型别名是一个名字,它是某种类型的定价。有两种方法定义类型别名:

      1.使用typedef关键字,如:

                typedef int *Int_Ptr

                Int_Ptr p=nullptr;   //Int_Ptr是一个int指针类型,这里定义了一个int型指针P

      2.使用别名声明(使用using).如:

                using Int_Ptr=int*;

                Int_Ptr p=nullptr;   //与上面完全一样

二:auto类型说明符

        编程时,常常需要把表达式的值赋给变量,于是就要求在声明变量时必须知道表达式的类型。然而有的时候并不容易知道表达式的类型。c++11中引入了auto类型说明符,用它就可以让编译器与分析变量的具体类型:

        int i=3,j=4;

        auto item=i+j;  //这时候编译器检验i+j得到的是一个整型,于是auto推断出了item是整型。

使用auto也可以在一条语句中声明多个变量,因为一条语句中最多只有一个数据类型,所以该语句中所有变量初始化必须是一样的:

        auto a=2.23,b=0.25;  //正确,a,b都是double类型

        atuto c=5,d=2.9 ;     //错误,c,d类型不同

     常量表达式(const expression):是指值不会改变并且在编译过程中就得到计算结果的表达式。(运行得到结果的不能成为常量表达式)。

             const int i=3;    //是一个常量表达式

             const int j=i+1; //是一个常量表达式

             int k=23;        //k的值可以改变,从而不是一个常量表达式

             const int m=f(); //不是常量表达式,m的值只有在运行时才会获取。

    constexpr变量

        C++11允许声明constexpr类型来由编译器检验变量的值是否是一个常量表达式。声明为constexpr的必须是一个常量,并且只能用常量或者常量表达式来初始化

             constexpr int i=3;

             constexpr int j=i+1;

             constexpr int k=f(); //只有f()是一个constexpr函数时k才是一个常量表达式

一般来说,若果一旦认定变量是一个常量表达式,那就把它声明为constexpr类型

尽管指针和引用都可以定义为constexpr,但是他们的初始值却受到了严格的限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储某个固定地址的对象。函数体中定义的变量并非存放在固定地址中,因此constexpr指针不可以指向这样的变量。相反的,对于所有函数体之外的对象地址是固定不变的,可以用constexpr来定义;

必须明确一点,在constexpr声明中,如果定义了一个指针,限定符号constexpr仅仅对指针有效,与指针所指对象无关。

    const int *p=nullptr;  //p是一个指向整型常量的指针(pointer to const)

    constexpr int *p1=nullptr; //p1是一个常量指针(const pointer)

const的限定

    const对象一旦创建后,其值就不可以改变,所以const对象必须初始化。与非const的类型比较,主要区别在于const类型对象执行但是不改变其操作内容。

    在默认状态下,const只在文件内有效。在默认状况下,const定义的对象仅限定在文件类起作用,当多个文件中出现了同名的const时其实等同于在不同文件中分别定义了独立的变量。于是我们只在一个文件中定义const对象,而在其他文件中仅仅声明就可以。对于const对象不管是声明还是定义都加extern关键字,这样只需要定义一次就可以了。如下:

        //在file1.h文件里面定义i

        extern const int i=3;    //将i定义为常量3

      //在file2.h中引用i时只要声明就可以,避免重定义

        extern const int i;

         int a=i;   //这样就可以使用i了

const的引用被称为对常量的引用(reference to const),引用类型必须与其所引用的对象保持一致,但是在这里有两个例外,第一个例外是在初始化常量引用时允许用任意表达式作为初始值。允许为一个常量引用绑定非常量对象,字面值,甚至是一个表达式:

                          int i=42;

                          const int &r1=i;    //正确,此时r1与i绑定

                          const int &r2=42;  // 正确,此时绑定一个字面值42

                          const int &r3=r1*2;  //正确,r3是一个常量引用,绑定字面值84

                           int &r4=42;    //错误,非常量引用不可绑定字面值

 指向常量的指针不可以用于改变它所指的对象的值。要想存放指向常量的地址,必须使用指向常量的指针。

                          const int i=9;

                          int *p1=&i   //错误,不可以用一个普通指针指向一个常量

                          const int*p2=&i;  //正确,用常量指针指向常量

                           *p2=5;   //错误,常量指针不可以改变所指向位置的值

常量指针(const pointer)必须初始化,一旦初始化完成,它指向的位置(存放的地址)就不在改变。

                           int i=4;

                           int j=5;

                           int *const p=&i;   //p将一直指向i,p是一个常量指针

                           const int*p1=&j;  //p1是一个指向常量的指针,注意const的位置

void*是一种特殊的指针类型,可以用来存放任意对象的地址。一个void*指针存放着一个地址,这一点和其他指针类似。不同的是,我们对它到底储存的是什么对象的地址并不了解;

   比如:double a=2.3;

            int b=5;

            void *p=&a;

            cout<<p<<endl;   //输出了a的地址

            p=&b;

            cout<<p<<endl;   //输出了b的地址

             //cout<<*p<<endl;这一行不可以执行,void*指针只可以储存变量地址,不冷直接操作它指向的对象

利用void*可以直接做的事比较有限:拿他和别的指针比较,作为函数的输入或者输出,或者赋值给另外一个void*的指针。不可以操作void*指向的对象。如此一来,内存空间就仅仅是内存空间,没办法访问内存空间指向的对象。

 抽象类就是在类中至少声明一个纯虚函数,所谓纯虚函数就是被标明不具体实现的函数。声明纯虚函数方法是在虚函数后面加上“=0”,如virtual void fn()=0;抽象类是作为基类为其他类而服务的,不可具体实例化

在类的编程中,要能进行抽象编程,不随类的改动而改动,类机制必须解决这个问题。在C++中那就是虚函数机制。基类与派生类的同名操作只要标记上virtual(虚拟)就可以),则该操作就具有多态性。

   在调用子类和基类同名虚函数标志函数时,使得该捆绑操作滞后运行,以实际对象类型来捆绑其对应成员函数的操作,此时做一个指向实际对象的成员函数的间调用。于是实际对象若是基类则调用基类成员函数,若是子类则调用子类成员函数。当然每个对象都额外占有一个指针空间来指向类中的虚函数表。使用了虚函数类比不使用虚函数类多了一个指针空间,当然这不算什么,但是涉及的操作,间接访问虚函数,对象指针偏移量计算等,所以采用了虚函数,会影响一些程序运行的效率。

   编译器遇到虚函数调用时会做一个滞后处理。由于间接访问比直接访问饶了一个弯,于是付出了时间代价和保存若干指针的空间代价,为了类编程随时体现多态性,只要继承结构要尽量将成员函数设计为虚函数。在一些讲究性能的小规模编程中,也用该不设置为虚函数。在偏向应用型的Java中,一切类都规定为虚函数性质,程序员链选择virtual的机会都没有,当然也影响了java的性能。

   虚函数在继承关系下会传递下去,这说明子类的成员函数前的virtual可以省略。另外要区分重载,覆盖,重载时函数名相同但是参数不同,而覆盖是两者都相同,如果子类出现同名重载的函数加上virtual也不会出现多态。

  关于虚函数的限制问题:

  1.只有类的成员函数才可以声明为虚函数,这是由于虚函数仅仅适合于有继承关系的类对象,所以普通函数是不可以声明为虚函数的。

  2.静态成员函数不可以是虚函数,因为静态成员函数不受对象的捆绑,没有任何的对象信息

  3.内联函数不可以是虚函数,因为内联函数是不可以在运行时动态的确定其位置的。

 4.构造函数不可以是虚函数,构造时对象还没完全出现,谈不上与对象捆绑

 5.析构函数可以是虚函数,而且常声明为虚函数,例如当基类指针了能指向不同的子类对象时,以该指针捆绑实现释放空间操作,应该是针对不同类的析构函数。

void f(std::vector<Base*>v)(){......}

//--------------------

for(i=0;i<v.size();i++)

    delete v[i];

上面程序delete v[i]在实施时候,会有相应调用捆绑的析构函数。向量中的元素有指向基类,有指向子类对象,所以在析构是应该指向不同对象做不同的析构工作。

继承关系·

     C++继承方式有公有继承,私有继承,保护继承。值得注意的是,基类私有成员在任用任何继承方式下都是隔离的,也就是视派生类为外人。在公有继承中,基类的每个成员在子类中保证相同的访问方式,在基类为public 成员,则在子类也是public成员,在基类为保护成员则在子类也是保护成员。对于保护继承,基类的公有成员和保护成员继承到子类都变成了保护成员。

   C++具有单一继承和多重继承。多重继承在实现时并不容易,主要是编译问题,模糊性问题,调试问题也很多,一般只有高级程序员才使用多重继承。如下例子:

#include<iostream>
using namespace std;

class Furniture{
protected :
double weight;
public:
void setWeight(double i){ weight = i; }
double getWeight(){ return weight; }
};

class Bed :virtual public Furniture{
public:
void sleep();
};

class Sofa :virtual public Furniture{
public:
void wacthTV(){};
};

class SleeperSofa :public Sofa, public Bed{
public:
void floadOut(){};
};

int main(){
SleeperSofa f;
f.setWeight(1.0);
}

上面程序中,Furniture是最初的基类,Bed类和Sofa类都从Furniture虚拟继承而来,而SleepSofa则继承于Bed和Sofa类,若不加上virtual关键字,则会产生歧义,这是由于在子类SleepSofa中继承的weight和setWeight到低来自哪里?是继承自Bed类还是,Sofa类,这样就产生了模糊不清的问题,如今的SleepSofa继承了Furniture的两个副本。当加上virtual关键字后,表明,Bed类和Sofa类都从Furniture虚拟继承,这样后代就保证了只继承一个副本,就不会产生歧义。

对象第复制operator=

  类机制中有默认的对象复制操作符=,自定义对象复制需要注意一个问题,如果有遇到指针指向的资源是需要释放的,这时需要毫不留情释放,否则内存空间的泄露就不可避免。复制操作与拷贝构造函数的参数是一致的,只是在功能上复制操作只管复制不管构造。赋值操作返回必须是引用返回,这是为了与复制操作符语义一致,因为复制操作的结果是一个可以地径操作的左值。

#include<iostream>
using namespace std;
class Student{
char* pname;
public:

Student(char *pname = "NoName"){
cout<<"成功构造"<<endl;
this->pname = new char[strlen(pname)+1];
strcpy(this->pname, pname);
}
void print(){cout<<pname<<endl;}

//--------------------------------------------
Student& operator=(const Student&s){
if(this==&s)
return *this;
delete []pname;
this->pname=new char[strlen(s.pname)+1];
strcpy(pname,s.pname);
return *this;
}
//----------------------------------------------
~Student(){
delete[]pname;
}
};
int main(){
Student a("LiLy");
Student b;
b=a;
b.print();
return 0;
}

    1默认拷贝构造函数:默认拷贝构造函数是构造函数的重载,它是依据对象来创建对象的,如Student a(b); 表示通过b对象来初始化a,那么这种对象创建活动为拷贝构造函数。如果对象实体是单纯的对象本体时,对象的拷贝构造与变量的拷贝并无两样,但是若对象本体不同于对象实体时,对象的拷贝就有了差别(如指针的拷贝)。如下:

#include<iostream>
using namespace std;
class Student{
char* pname;
public:
Student(char *pname = "NoName"){
cout<<"成功构造"<<endl;
this->pname = new char[strlen(pname)+1];
strcpy(this->pname, pname);
}
~Student(){
delete[]pname;
}
};
int main(){
Student a("LiLy");
Student b(a);
return 0;
}

在默认拷贝构造时,pname指针也复制了,a对象与b对象的pname都指向同一个"Lily",此时在析构时候,"Lily"这块区域被删除2次,于是系统会出错。这是由于a对象本体与实体不一样所导致的。此时需要重写拷贝构造函数覆盖默认拷贝构造函数:

Student (const Student &s){

     pname=new char[strlen(s.pname)+1];

    strcpy(pname,s,pname);

}

自定义的拷贝构造函数也是类名,他是构造函数的重载。拷贝构造函数必须使用对象的常引用。当自定义对象作为参数传递时,能用引用就尽量用引用,能用常量引用的就用常量引用。

    静态属性不是类中每个对象拥有的,而是共有的。由于静态成员逃离了对象而存在的性质,所以该实体应该在所有对象产生之前产生,更适合的时机是在程序启动的时候做初始化。初始化时候不可重复上static,但是要加上类名空间。该实体在程序中的唯一性,要求他不可以和类的定义放在头文件中,但是它确实是类的成员,所以放在类实现中最合适。如下:

//--------------------Student.h ---类定义的文件

class Student{

private:

   static int num;

}

//-------------------Student.cpp---内实现的文件

#include"Student.h"

Student::number=0;

由于静态数据成员不属于任何一个对象,为了安全起见将静态数据成员定义为私有的,于是如何去访问它呢?同样的可以定义静态成员函数来访问它,用静态成员函数去访问静态数据是最合适不过的,在类中声明静态成员函数,要在成员函数前加上static关键字,可以用对象来调用他,也可以用雷明加域操作符来调用。静态成员函数实现部位应该和成员函数位置相同,在类外部实现时要免去static,但是域操作符是必须的。

//--------------------Student.h ---类定义的文件

class Student{

private:

   static int num;

public:

static void Print();

}

//-------------------Student.cpp---内实现的文件

#include"Student.h"

Student::number=0;

void Student::Print(){

   cout<<num++<<endl;

内联函数(inline)

内联函数声明必须在调用前,这是由于内联函数的代码在程序运行时是直接镶嵌在调用处执行的,这用节省啦函数来回跳动的时间。应该注意的是,内联函数函数体应该尽量小,一般没有循环语句,否则编译器无视内联,代码在5行内。适合于频繁被调用的小程序。

#include<iostream>
using namespace std;
int sum(int a, int b){
return a + b;
}
inline int sum2(int a, int b){
return a + b;
}
int main(){
int i,j;
for (i = 0; i < 10000; i++)
for (j = 0; j < 10000; j++)
sum(2, 3);
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值