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; 这一句,该指针很明显的是指向父类,那么肯定调用的是父类的方法。

原文地址:https://www.cnblogs.com/lzy820260594/p/11315400.html