C++相关知识学习(2021.6.29)

一、C++程序结构

         程序由多个程序源文件组成,C++程序源文件(.cpp)为扩展名,该文件由编译预处理指令、数据或数据结构定义及若干个函数组成,程序执行时都必须从唯一的main()主函数开始。例如:

/*一个简单的C++程序*/
#include <iostream.h>
int main()
{
    
    
	double a,b;
	cout<<"输入一个整数:";
	cin>>a;//从键盘上输入变量a的值
	b=a*a;//计算a的平方
	cout<<"计算a的平方为:"<<b<<"\n";//输出b
	return 0;
}

        #include后的<iostream.h>是C++编译器自带的库文件,被称为头文件(Header File),它定义了标准输入/输出流的相关数据及其操作,可在程序中用到输入/输出流对象cin和cout。此外,C++编译器自带头文件能够用于实现基本输入/输出、数值计算和字符串处理等操作。C++中/*…*/用于实现多行的注释,也称为块注释。而//只能实现单行注释,称为行注释。注释的目的只是为了提高程序的可读性,对编译和运行并不起作用。

二、C++预处理

        C++预处理命令有三种:宏定义命令文件包含命令条件编译命令。在C++程序中,每一条预处理命令以#开头且必须单独占用一行,同时行尾还不允许有分号;

宏定义命令

        用#define可以定义一个符号常量,例如#define PI 3.141592653#define作为宏定义命令可将3.141592653用PI代替,其中PI宏名。注意:(1)#definePI和3.141592653必须以空格分开,宏名通常被定义为大写;(2)宏不允许再重新定义,当使用命令#undef 宏名时才可重新定义;(3)已定义过的宏名可用于定义新的宏;(4)宏还可以定义为带参数的函数#define MAX(a,b) ((a)>(b)?(a):(b)),程序中的x = MAX(3,9);在预处理后将变为x = (3>9?3:9);

文件包含命令

        C++中的文件包含#include命令是将其他源文件合并到现在的源程序中,包含两种格式:#include <文件名>#include "文件名"。通常将以.h为扩展名的文件称为头文件,将以.cpp为扩展名的文件称为源文件。尖括号< >用来包含系统提供的头文件;双引号""用于包含用户自定义的头文件或源文件。

条件编译命令

        条件编译可根据一定的条件来编译源文件的不同部分,使得同一源程序在不同的编译条件下得到不同的目标代码。条件编译命令的三种常用形式如下:

  • 格式1
#ifdef <标识符>
    <程序段1>
[
#else
    <程序段2>
]
#endif
  • 格式2
#ifndef <标识符>
    <程序段1>
[
#else
    <程序段2>
]
#endif
  • 格式3
#if <表达式1>
   <程序段1>
[
#elif <表达式2>
   <程序段2>
   ...
]
[
#else
   <程序段n>
]
#endif

三、C++异常处理

        程序错误一般包括语法错误逻辑错误运行时异常(Exception)
        语法错误指函数、类型、语句、表达式、运算符或标识符等的使用不符合C++语法而造成的错误,程序编译或链接时就由编译器指出。
        逻辑错误指程序虽然可以成功运行但并未能实现或达到预期功能或结果,可通过调试或测试才能够解决;
        运行时异常指在程序运行中由于不可预料异常事件(如内存空间不足、文件不存在、零除数或下标越界等)的发生而导致程序异常中止。
        异常处理try/throw/catch指在程序中对运行时异常进行预见性处理从而保证程序的健壮性。(注:try与catch一同出现,一个try语句块后至少应有一个catch语句块,异常类型表达式可以是类对象、常量或变量表达式等,catch语句块中的形参类型可以是C++基本类型(如intlongchar等)、构造类型,还可是已定义类,如类的指针或者引用类型等。

try
{
    
    
	//被侦测的语句
}
throw [异常类型表达式]
catch (形参类型 [形参名])
{
    
    
	//异常处理语句...
}
try
{
    
    
	throw "除数不能为0";
}
catch(const char * s) //指定异常形参名
{
    
    
	cout<<s<<endl; //使用异常形参名
}
try
{
    
    
	...
} catch(...)
{
    
    
	...
} catch(...)
{
    
    
	...
}

        throw和catch如同函数调用的关系,catch指定形参,throw给出实参。throw可以出现在任何需要的地方,只要最终有catch可以捕获它即可。例如:

class Overflow
{
    
    	//...
public:
	Overflow(char,double,double);
};
void f(double x)
{
    
    	//...
	throw Overflow('+',x,3.45e107); //在函数体中使用throw,用来抛出一个对象
}
try
{
    
    	//...
	f(58.67);
	//...
} catch(Overflow& op)
{
    
    
	//处理Overflow类型的异常
}
try
{
    
    
	...
} catch(int)
{
    
    
	throw "hello exception"; //抛出一个新的异常,异常类型为const char *
} catch(float)
{
    
    
	throw; //重新抛出当前的float类型异常
}

四、C++面向对象编程

        作为一种计算机编程架构,为了达到了软件工程中重用性灵活性扩展性这三个主要目标,面向对象编程Object Oriented Programming, OOP)的基本原则是程序由单个可起到子程序作用的单元或对象组合而成。。

基本概念

        面向对象编程中的概念有对象封装继承动态绑定多态性数据抽象消息传递等。
        是同类对象的抽象,对象所包含的全部数据和代码都可通过类来构造。class是类声明的关键字,它的后面是要声明类的名称,类的成员有数据成员和成员函数。类中的关键字公有public 、私有private和受保护protected声明了类中的成员与类外对象之间的关系,称为访问权限。当类的成员函数的定义是在类体外部完成时,必须使用作用域运算符::来告知编译系统该函数所属的类。

class <类名> //声明部分
{
    
    //类体
	private[<私有型数据和函数>]
	public:
		[<公有型数据和函数>]
	protected:
		[<保护型数据和函数>]
}<各个成员函数的实现> //实现部分
<函数类型> <类名>::<函数名>( <形式参数表> )
{
    
    //函数体
	...
}

        对象是封装了数据和操作这些数据的代码的逻辑实体。类声明后就可以定义该类的对象,类对象有三种定义方式:声明之后定义声明之时定义一次性定义。为了提高程序的可读性,在程序中应尽量使用对象的声明之后定义方式<类名> <对象名表>;。类名是已声明过的类的标识符,对象名可以有一个或多个,多个对象时要使用逗号隔开。被定义的对象既可以是一个普通对象,也可以是一个数组对象或指针对象。

class A{
    
     ...}; 
A a; //声明之后定义
class B
{
    
    
	...
} b, c; //声明之时定义
class
{
    
    
	...
} d, e; //一次性定义
CStuscore one, *Stu, Stus[2];

        对象成员的访问操作方法:对象成员即该对象的类所定义的数据成员成员函数

<对象名>.<成员变量>
<对象名>.<成员函数>(<参数表>)
cout<<one.getName()<<endl; //调用对象one中的成员函数getName
cout<< Stus[0].getNo()<<endl; //调用对象数组元素Stus[0]中的成员函数

        一个类对象只能访问该类的公有型成员,不能访问私有型成员。若对象是一个指针,则对象的成员访问形式如下,->是另一个表示成员的运算符,它与.运算符的区别是:->用来表示指向对象的指针的成员,而.用来表示一般对象的成员。

<对象名>-><成员变量>
<对象名>-><成员函数>(<参数表>)

        下面的两种表示是等价的(对于成员函数也适用):

<对象指针名>-><成员变量>
(*<对象指针名>).<成员变量>
CStuscore *two = &one;
cout<<(*two).getName()<<endl; //A
cout<<two->getName()<<endl; //与A等价

        封装是将数据和代码捆绑在一起以避免外界的干扰和不确定性。对象的某些数据和代码可以是私有的,不能被外界访问,以此实现对数据和代码不同级别的访问权限。
        继承是使某个类型的对象获得另一个类型的对象的特征。在C++中,类的继承具有下列特性。(1)单向性:类的继承是有方向的;(2)传递性:正因为继承的传递性,才使子类自动获得基类的属性和方法;(3) 可重用性:派生类具有所有代基类的属性和方法,而不必从头开始重新定义和设计,类的继承机制也体现了代码重用或软件重用的思想。C++中派生类定义格式如下:

class <派生类名>:[<继承方式1>] <基类名1>,[<继承方式2>] <基类名2>,... //基类列表
{
    
    
	[<派生类的成员>]
};

              C++中类的继承方式有三种:public(公有)private(私有)protected(保护)。派生类中的数据成员通常有三类:基类的数据成员派生类自身的数据成员派生类中其他类的对象。通常将派生类中的基类,称为基类拷贝,或称为基类子对象。C++规定,派生类中对象成员初值的设定应在初始化列表中进行,因此一个派生类的构造函数的定义可有下列格式:

<派生类> (形参表)
	:基类1(参数表),基类2(参数表),...,基类n(参数表),
	对象成员1(参数表),对象成员2(参数表),...,对象成员n(参数表)
{
    
     }

        多态是指不同事物具有不同表现形式的能力,多态机制使具有不同内部结构的对象可以共享相同的外部接口,通过这种方式减少代码的复杂度。在C++中,多态具体体现在运行时和编译时两个方面程序运行时的多态是通过继承和虚函数体现的,它在程序执行之前,根据函数和参数无法确定应该调用哪一个函数,必须在程序执行过程中,根据具体的执行情况动态地确定;而在程序编译时的多态体现在函数的重载和运算符的重载上
        虚函数是利用关键字virtual修饰基类中的publicprotected的成员函数。当在派生类中进行重新定义后,就可在此类层次中具有该成员函数的不同版本。在基类中,虚函数定义的一般格式如下:

virtual <函数类型> <函数名>( <形式参数表> )
{
    
    //函数体
	<若干语句>
}

        对象之间需要相互沟通,沟通的途径就是对象之间收发信息。消息内容包括接收消息的对象的标识、需要调用的函数的标识和必要的信息。this指针是类中的一个特殊指针。当类实例化(用类定义对象)时,this指针指向对象自己;而在类的声明时,指向类本身。

类的构造函数

        编译器会默认为C++的空类创建四个成员函数:构造函数析构函数拷贝构造函数赋值函数。构造函数的功能进行对象的初始化,析构函数的功能是释放并清理对象内存。

class CName
{
    
    
	public:
	string cname;
	CName(string s)
	{
    
    
		cname = s;
	}
};
CName c1("DING"); //A:通过构造函数设定初值
CName c2(c1); //B:通过指定对象设定初值:     <类名> <对象名 1>(<对象名 2>)

        上述例子中,B语句是将c1作为c2的初值,同c1一样,c2这种初始化形式要调用相应的构造函数,但找不到相匹配的构造函数。事实上,CName还隐含一个特殊的默认拷贝构造函数,其原型为Cname(const CName &)。仅仅将内存空间的内容复制的方式称为浅拷贝。对于数据成员有指针类型的类来讲,默认拷贝构造函数则无法解决,所以必须自己定义一个拷贝函数,在进行数值复制之前,为指针类型的数据成员另辟一个独立的内存空间。由于这种复制还需另辟内存空间,因而称其为深拷贝拷贝构造函数是一种特殊的构造函数,除遵循构造函数的声明和实现规则外,C++规定拷贝构造函数左起的第一个参数必须是类的引用对象,它可以是类名 &对象或是const 类名 &对象形式。如果用户在类中自己定义了拷贝构造函数,那么隐式默认拷贝构造函数和隐式默认构造函数将都不再有效。

<类名> (参数表)
{
    
     }
CName {
    
     CName &x}; //x为合法的对象标识符
CName {
    
     const CName &x };
CName {
    
     CName &x, ...); //...表示还有其他参数
CName {
    
     const CName &x, ...};

模板类

        可以使用模板类创建对一个类型进行操作的类家族。下面例子中的模板类使用了两个参数,即一个类型T和一个整数iT参数可以传递一个类型,包括结构和类;i参数传输一个整数,在编译时是一个常数,用户可利用标准数组声明来定义长度为i的成员。

template <class T, int i> class TempClass
{
    
    
public:
	TempClass( void };
	~TempClass( void );
	int MemberSet{
    
     T a, int b );
private:
	T Tarray;
	int arraysize;
}

继承

        继承可使新类获得父类的操作和数据结构,用户只需在新类中增加原有类中没有的成分便可扩展类的功能。常用的三种继承方式包含publicprivateprotected
        (1)公有继承(public)方式:在公有继承时,派生类的对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有成员和保护成员。
        (2)私有继承(private)方式:在私有继承时,基类的成员只能由直接派生类访问,而无法再向下继承。
        (3)保护继承(protected)方式:在保护继承时,基类的成员只能由直接派生类访问,而无法再向下继承。
        (4)多重继承及虚继承多重继承是一个类从多个基类派生而来的能力,派生类实际上获取了所有基类的特性。在面向对象的程序设计中,继承和多重继承通常指公共继承。在继承中,基类的private对所有的外界都屏蔽(包括自己的派生类),基类的protected控制符对应用程序是屏蔽的,但对其派生类是可访问的。虚继承是多重继承中特有的概念。虚拟基类是为了解决多重继承而出现的。

class A;
class B:public virtual A; //虚继承
class C:public virtual A; //虚继承
class D:public B, public C;
        类D继承自类B和C,而类B和类C都继承自类A,因此等同于如图A.2所示。在类D中会两次出现A。为了节省内存空间,可以将B、C对A的继承定义为虚拟继承,而==A就成了虚拟基类==。最后形成如图A.3所示的情况。

多态

        狗叫、猫叫、猪叫,这里的“”就是多态(Polymorphism)。作为面向对象编程领域的核心概念,多态性可以概括为“一个接口,多种方法”,在程序运行过程中才去决定需调用的函数。多态性允许将子类类型的指针赋值给父类类型的指针,在C++中是通过虚函数(Virtual Function)实现的。
        虚函数是允许被子类重新定义的成员函数。封装可以隐藏功能的实现细节以实现代码的模块化;继承能够对现存类进行扩展。它们的目的都是为了代码重用。而多态则是为了实现接口重用。例如,实现一个Animal类,使其成为抽象数据类型。类Dog和类Pig都是从类Animal里派生的。

class Animal
{
    
    
public:
	virtual void Zou()=0;
	virtual void Jiao()=0;
};
class Dog : public Animal
{
    
    
public:
	virtual void Zou();
	virtual void Jiao();
};
class Pig : public Animal
{
    
    
public:
	virtual void Zou();
	virtual void Jiao();
};

猜你喜欢

转载自blog.csdn.net/jing_zhong/article/details/118336412