zoukankan      html  css  js  c++  java
  • c和c++精炼总结(重点是几个重要的关键字的用法)

    1、cin输入多个数据用空格或者回车来区分,不可以用“,”来区分。

    2、多个判断语句下,if...if...else;这样写程序会导致最后两个形成独立判断,也就是说,如果第一个if成立,那么除了执行第一个if下的内容,还会执行else下的内容;为了避免这样的问题,就需要用if...else if...else这样的嵌套

    3、随机数函数的应用

    (1)srand(time(0))和rand();前者是为产生随机数提供种子函数,后者就是随机值产生的函数;而套用的time(0),目的是为了    产生随着时间改变的随机数。

    (2)这两个函数分别属于两个头文件 #include <stdlib.h>、#include<time.h>

    4、在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):

    (1) 一个对象作为函数参数,以值传递的方式传入函数体;
    (2)一个对象作为函数返回值,以值传递的方式从函数返回;
    (3)一个对象用于给另外一个对象进行初始化(常称为赋值初始化);

    5、关于extern、static、const和virtual的用法总结:

    extern:

    (1) extern修饰变量的声明。

    如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。

    这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明extern int v,还取决于变量v本身是能够被引用到的。

    这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量

    还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

    (2) extern修饰函数声明。

    从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。

    如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。

    就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。

    对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。

    这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

    (3)此外,extern修饰符可用于指示C或者C++函数的调用规范。

    比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。

    这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

    static:

    (1)、 局部静态变量

    局部变量按照存储形式可以分为三种,分别是auto、static、register。

    与auto类型(普通)局部变量相比,static有三点不同:

    1. 存储空间分配不同

    auto类型分配在栈上,属于动态存储类别,占动态存储空间,函数调用结束后自动释放;static类型分配在静态存储区,在程序整个运行期间都不释放;

    两者作用域相同,但是生存期不同。

    2. static局部变量在初次运行时进行初始化工作,且只初始化一次。

    3. 对于局部静态变量,如果不赋初值,编译期会自动赋初值0或者空;

    auto类型的初值是不确定的。

    对于C++的类对象例外,class的对象实例如果不初始化,则会自动调用默认构造函数,不管是不是static类型。

    特点:static局部变量的“记忆性”与生存期的“全局性”

    所谓“记忆性”是指在两次函数调用时,在第二次调用进入时,能保持第一次调用退出时的值。

    #include <iostream>
    using namespace std;
     
    void staticLocalVar()
    {
    	static int a = 0;
    	cout<<"a="<<++a<<endl;
    }
     
    int main()
    {
    	staticLocalVar();	// a=1
    	staticLocalVar();	// a=2
    	system("pause");
    	return 0;
    }
    

      利用生存期的”全局性“改善return a pointer / reference to a local object的问题,local object的问题在于退出函数时,生存期就结束,局部变量就会被销毁;利用static就可以延长局部变量的生存期。

    // IP address to string format 
    // Used in Ethernet Frame and IP Header analysis 
    const char * IpToStr(UINT32 IpAddr) 
    { 
    	static char strBuff[16]; // static局部变量, 用于返回地址有效 
    	const unsigned char *pChIP = (const unsigned char *)&IpAddr; 
    	sprintf(strBuff, "%u.%u.%u.%u",  pChIP[0], pChIP[1], pChIP[2], pChIP[3]); 
    	return strBuff; 
    } 
    

      注意事项:

    1. “记忆性”是程序运行很重要的一点就是可重复性,而static变量的“记忆性”破坏了可重复性,造成不同时刻同一函数的运行结果不同。

    2. “生存期”全局性和唯一性。 普通的局部变量在栈上分配空间,因此每次调用函数时,分配的空间都可能不一样,而static具有全局唯一性的特点,每次调用时都指向同一块内存,这就造成一个很重要的问题---不可重入性!!!

    在多线程或者递归程序中要特别注意。

    (2) 外部静态变量/函数

    在C中static的第二种含义:用来表示不能被其它文件访问的全局变量和函数。

    此处static的含义是指对函数的作用域仅仅局限于本文件(所以又称为内部函数)。

    注意:对于外部(全局)变量,不论是否有static限制,它的存储区域都是在静态存储区,生存期都是全局的,此时的static只是起作用域限制作用,限制作用域在本文件内部。

    使用内部函数的好处是:不同的人编写不同的函数时,不用担心函数同名问题。

     //file1.cpp 
    static int varA; 
    int varB; 
    extern void funA() 
    {  
    } 
     
    static void funB() 
    { 
    } 
     
    //file2.cpp 
    extern int varB; // 使用file1.cpp中定义的全局变量 
    extern int varA; // 错误! varA是static类型, 无法在其他文件中使用 
    extern void funA(); // 使用file1.cpp中定义的函数 
    extern void funB(); // 错误! 无法使用file1.cpp文件中static函数 
    

      (3)静态数据成员/成员函数(C++特有)

    C++重用了这个关键字,它表示属于一个类而不是属于此类的任何特定的对象的变量和函数。

    静态类成员包括静态数据成员和静态函数成员。

    1. 静态数据成员

    类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时静态数据成员还具有以下特点。

    1) 静态数据成员的定义

    静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。其定义方式与全局变量相同。举例如下:

     xxx.h文件        
     class base
    {           
    private:  	 
    	static const int _i;	//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。  
    };   
     
    xxx.cpp文件          
    const int base::_i = 10;	//定义(初始化)时不受private和protected访问限制. 
    

      注:不要试图在头文件中定义(初始化)静态数据成员。在大多数情况下,这会引起重复定义。即使加上#ifndef  #define  #endif或者#pragma once也不行。

    2) 静态数据成员被类的所有对象所共享,包括该类的派生类的对象。

    #include <iostream>
    using namespace std;
     
    class base
    {                
    public:               
    	static int _num;	//声明          
    };  
            
    int base::_num = 0;	//静态数据成员的真正定义     
            
    class derived : public base
    {          
    };    
         
    int main()          
    {             
    	base a;             
    	derived b;			
    	a._num++;             
    	cout<<"base class static data number _num is "<<a._num<<endl;	// 1           
    	b._num++;            
    	cout<<"derived class static data number _num is "<<b._num<<endl;// 2
    	system("pause");
    	return 0;
    }
    

      3) 静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。

    class base
    {  
    public:            
    	static int _staticVar;  
    	int _var;  
    	void foo1(int i = _staticVar);//正确,_staticVar为静态数据成员    
    	void foo2(int i = _var);//错误,_var为普通数据成员  
    };  
    

    4 )  ★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为所属类类型的指针或引用。举例如下:

    class base
    {            
    public:          
    	static base _object1;//正确,静态数据成员         
    	base  object2;//错误        
    	base *pObject;//正确,指针     
    	base &mObject;//正确,引用    
    };
    

       5 ).★这个特性,我不知道是属于标准c++中的特性,还是vc6自己的特性。  

     静态数据成员的值在const成员函数中可以被合法的改变。举例如下:

    class base
    {            
    public:            
    	base()
    	{
    		_i = 0;
    		_val = 0;
    	}           
    	mutable int _i;        
    	static int _staticVal;        
    	int _val;        
    	void test() const
    	{  
    		_i++;//正确,mutable数据成员             
    		_staticVal++;//正确,static数据成员                
    		_val++;//错误            
    	}        
    };        
    int   base::_staticVal = 0; 
    

       2 静态成员函数    

    1).静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数指针来储存。举例如下:

    class base
    {               
    	static int func1();               
    	int func2();            
    };  
             
    int (*pf1)() = &base::func1;		//普通的函数指针         
    int (base::*pf2)() = &base::func2;	//成员函数指针  
    

      2).静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。  
           3).静态成员函数不可以同时声明为   virtual、const、volatile函数。举例如下:

    class base
    {               
    	virtual static void func1();//错误     
    	static void func2() const;//错误      
    	static void func3() volatile;//错误          
    }; 
    

      最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。

     const

    (1) const的基本功能与用法

    1).将限定符声明为只读

    使用方法如下,在类型前/后加上关键字const,该变量必须被初始化,否则编译错误;该变量不能被重新赋值,否则也编译错误。 

    const int i = 50;   // 编译正确
    const int j;        // 编译错误
    int k = 0;
    i = k;              // 编译错误
    k = i;              // 编译正确

    2).用于修饰函数形参,保护参数使其不被修改

    用法1:若形参为const A* a,则不能改变函数所传递的指针内容,这样对指针所指向的内容起到保护作用,这里需要注意的是,该修饰不能改变指针所指向地址所存储的内容,但是指针a所指向的地址可以被改变,具体例子如下:

    void Test(const int *a)
    {
        *a = 1;          //错误,*a不能被赋值
        a = new int(10086);  //正确,为指针a开辟新的空间,并令*a=10086
    }
    
    int main()
    {
        int *a = new int(10000);
        Test(a);
        return 0;
    }

    用法2:若形参为const A& a,则不能改变函数传递进来的引用对象,从而保护了原对象的属性。

    对于自定义的数据类型,用引用传递速度较快,如果不想改变原值,就可以用const来保护参数,如以下例子:

    void Test(const int &a) //保护L7中的a不会被改变
    {
        a = 2;//错误,a不能给常量赋值
    }
    
    int main()
    {
        int a = 3;
        Test(a);
        return 0;
    }
    

      

    事实上对于内置型数据类型(如以上例子中的int类型),用引用传递不会使速度更快。如果是用引用传参,一般是为了改变参数值;如果不想改变参数的值,直接值传递即可,不必使用const修饰。而对于自定义数据类型的输入参数,为了提高速度和效率,应使用“const + 引用传递”代替值传递。例如:

    将函数 void Test(A a) 改为-> void Test(const A &a)

    3).用于修饰函数返回值

    用法1:用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候 。
    举例:

    const Rational operator*(const Rational& lhs, const Rational& rhs) 
    { 
        return Rational(lhs.numerator() * rhs.numerator(), 
    lhs.denominator() * rhs.denominator()); 
    } 
    Rational a,b; 
    Radional c; 
    (a*b) = c;//错误

    用法2:不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到,具体例子如下:

    class A
    {
    public:
        int y;
        A(int y):x(x),y(y){};
        void Sety(int y){this->y = y;}
    };
    
    const A Test1(A a)
    {
        return a;
    }
    
    const A& Test2(A &a)
    {
        return a;
    }
    
    int main()
    {
        A a(2);
        Test1(a).Sety(3);//错误,因为Test1(a)的返回值是个const,不能被Sety(3)修改
        Test2(a).Sety(3);//错误,因为Test1(a)的返回值是个const,不能被Sety(3)修改
        return 0;
    }
    

    用法3:如果给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。例子如下:

    const char * GetString(void){}
    int main()
    {
        char *str1=GetString();//错误
        const char *str2=GetString();//正确
        return 0;
    }
    

      用法4:函数返回值采用“引用传递”的场合不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。例子如下:

    class A
    {
        // 以下赋值函数的返回值加const修饰,该返回值的内容不允许修改
        A &operate = (const A &other); 
    }
    A a, b, c;  // a,b,c为A的对象
    a = b = c;      // 正确
    (a = b) = c;    // 错误,a = b的返回值不允许被再赋值

    4).在类成员函数的函数体后加关键字const

    在类成员函数的函数体后加关键字const,形如:void fun() const; 在函数过程中不会修改数据成员。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。

    如果不是在类的成员函数,没有任何效果,void fun() const;和void func();是一样的。

    5). 在另一连接文件文件中引用常量

    方法:在类型前添加关键字extern const,表示引用的是常量,因此该常量不允许被再次赋值,举例如下:

    extern const int i;       // 正确
    extern const int j = 10;  // 错误,常量不可以被再次赋值

    (2).const常量与#define的区别

    1).const常量有数据类型,而宏常量没有数据类型

    宏常量只进行简单的字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误,如:

    #define I = 10
    const long &i = 10;
    // 由于编译器的优化,使得在const long i=10时i不被分配内存
    // 而是已10直接代入以后的引用中,以致在以后的代码中没有错误
    //一旦你关闭所有优化措施,即使const long i = 10也会引起后面的编译错误。
    char h = I; // 正确
    char h = i; // 编译警告,可能由于数的截短带来错误赋值

    2).使用const可以避免不必要的内存分配

    从汇编的角度来看,const定义常量只是给出了对应的内存地址, 而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。例子如下:

    #define k "Hello world!"
    const char pk[]="Hello world!";
    printf(k);      // 为k分配了第一次内存
    printf(pk);     // 为pk一次分配了内存,以后不再分配
    printf(k);      // 为k分配了第二次内存
    printf(pk);

    (3). 使用const的一些注意事项

    1).修改const 所修饰的常量值

    以下例子中,iconst修饰的变量,可以通过对i进行类型强制转换,将地址赋给一个新的变量,对该新的变量再作修改即可以改变原来const 修饰的常值。

    const int i = 0;
    int *p=(int*)&i;
    *p = 100;

    2).构造函数不能被声明为const

    3).const数据成员的初始化只能在类的构造函数的初始化表中进行

     

    class A
    {
    public:
        const int a;
        A(int x):a(x)//正确
        {
            a = x;//错误
        }
    };

    4).在参数中使用const应该使用引用或指针,而不是一般的对象实例

    合理利用const在成员函数中的三种用法(参数、返回值、函数),一般来说,不要轻易的将函数的返回值类型定为const;另外,除了重载操作符外一般不要将返回值类型定为对某个对象的const引用。

    5).对于使用const修饰来指针的情况

    对于以下情况,const放在变量声明符的前后位置效果是一样的,这种情况下不允许对指针a 的内容进行更改操作:

    int i;
    const int *a = &i;
    int const*a = &i;
    

      但是,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即该指针指向一个地址,该地址的内容不可变;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量:

    int i;
    // 以下一行表示a是一个指针,可以任意指向int常量或者int变量
    // 它总是把它所指向的目标当作一个int常量
    // 也可以写成int const* a
    const int *a = &i;
    // 以下一行表示a是一个指针常量,
    // 初始化的时候必须固定指向一个int变量
    // 之后就不能再指向别的地方了
    // 但是指针指向的内容可以改变
    int *const a = &i;
    

    6).指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如以下例子中a++是错误的: 

    int *const a = &i;
    a++;  // 错误,a指针本身是常量,不能再指向别的地方

    7).当指针本身和指针所指向的内容均为常量时

    这种情况下可写为:

    const int * const a = &i;
    

      8).const成员函数返回的引用,也是const

    #include<iostream>
    using namespace std;
    class A
    {
    public:
        int x;
        void set(int x){this->x = x;}
        // const成员函数返回的引用也是const,a
        // 如果把A&前面的const去掉会出错
        // 因为返回的是一个const的对象,返回类型却不是const
        // 返回的内容和返回的类型不符
        const A& Test1()const
        {
        // 错误。这是const成员函数的特点
        x = 2;
        // 不限于*this。不管返回的是什么,哪怕是一个定义为非const的对象,结果也是一样的
        return *this;
        }
    };
    
    int main()
    {
        A a, b;
        // 正确,虽然返回的是一个const,却用另一个非const来接收
        b = a.Test1();
        // 错误,既然是别名,那么别名的类型要与原来的类型相同
        A &c = a.Test1();
        // 正确虽然在a.Test1()中a不能改变,但是这里已经出了这个成员函数的作用域
        a.set(2);
        // 正确,b接收了a.Test1()返回的数据的内容,但是它不是const
        b.set(2);
        // 错误。a.Test1()是一个对象,这个对象是它的返回值
        // 虽然没有名字,但是它就是a.Test1()的返回值
        // 值是a.Test1()返回的值,类型是a.Test1()返回的类型
        a.Test1().set(2);
        return 0;
    }

    9).mutable将数据声明为可变数据成员

    在C++语言中,mutable是使用较少的关键字,它的作用是:如果一个函数被const 修饰,那么它将无法修改其成员变量的,但是如果一个成员变量是被mutable修饰的话,则可以修改。

    mutable 可以用来指出,即使成员函数或者类变量为const,其某个成员也可以被修改。反过来说,可变数据成员永远不能成为const,即使它是const对象的成员。

    class A
    {
    public:
        int x;
        mutable int y;
        A(int a, int b):x(a),y(b){}
    };
    
    int main()
    {
        const A a(0, 0); // const对象必须初始化
        a.x = 1;         // 错误
        a.y = 2;         // 正确,mutable修饰使得成员可被修改,即使对象a为const
        return 0;
    }
    

    virtual 

     先来一段代码:

    #include "stdio.h" 
    #include "conio.h"
     
    class Parent
     
    {
    	
    public:
    	
    	char data[20];
    	void Function1();	
    	virtual void Function2();   // 这里声明Function2是虚函数
    	
    }parent;
     
    void Parent::Function1()
    {
    	printf("This is parent,function1
    ");
    }
     
    void Parent::Function2()
     
    {
    	printf("This is parent,function2
    ");
    }
     
    class Child:public Parent
     
    {
    	void Function1();
    	void Function2();
    	
    } child;
     
    void Child::Function1()
     
    {
    	printf("This is child,function1
    ");
    }
     
    void Child::Function2()
     
    {
    	printf("This is child,function2
    ");
    }
     
    int main(int argc, char* argv[])
     
    {
    	Parent *p;  // 定义一个基类指针
    	if(_getch()=='c')     // 如果输入一个小写字母c	
    		p=&child;         // 指向继承类对象
    	else	
    		p=&parent;       // 否则指向基类对象
    	p->Function1();   // 这里在编译时会直接给出Parent::Function1()的入口地址。	
    	p->Function2();    // 注意这里,执行的是哪一个Function2?
    	return 0;
    	
    }

    用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:

    This is parent,function1

    This is child,function2

    为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。 那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。
    如果我们在运行上面的程序时任意输入一个非c的字符,结果如下:

    This is parent,function1

    This is parent,function2

    请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。

    PS:一定要注意“静态联翩 ”和“ 动态联编 ”的区别,对于我来说,若没有在VC6.0中亲自去测试,凭自己的感觉,当在键盘中输入“c”时,我会觉得由于有:p=&child;这句代码,我认为结果会是:

    This is child,function1
    This is child,function2
    

      但是实际结果却是:

    This is parent,function1
    This is child,function2

    因为虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实,它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。

    第二行中调用了子类的function2,完全是因为virtual 的功能,virtual实现了动态联编,它可以在运行时判断指针指向的对象,并自动调用相应的函数。当然,如果执行的是:p=&parent; 这一句,该指针很明显的是指向父类,那么肯定调用的是父类的方法。

  • 相关阅读:
    Flare3D游戏特效教程:火拳阿宝
    AS3:物体的运动
    Away3D基础教程(三):三维世界的灯光
    代码库工具:SVN
    磨刀不误砍柴工:取巧而已
    可视化组件:Swing JComponent
    软件简单升级方式:文件覆盖
    代码库工具:CVS
    多字符串查找算法:kmp与step
    格式化文本支持:JTextPane
  • 原文地址:https://www.cnblogs.com/lzy820260594/p/11315400.html
Copyright © 2011-2022 走看看