返回目录:金融新闻
C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。
C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。
(加群:590750544 学习c语言c++游戏开发——群里有大量游戏开发教学视频)
所以C与C++的最大区别在于它们的用于解决问题的思想方法不一样。之所以说C++比C更先进,是因为“ 设计这个概念已经被融入到C++之中 ”。
对语言本身而言,C是C++的子集。《Effective C++》上说道,C++由四个部分组成: C、Object-Oriented C++、Template C++、 STL,即c语言、面向对象OOP、泛型编程(模板)、STL。
面向对象OOP
面向对象的三个基本特征是:封装、继承、多态
封装
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
类的成员可以分为共有成员,私有成员,保护成员。在不考虑继承的情况下,保护成员与私有成员一样,都只能在类内部被访问;而存在继承的情况下,基类的保护成员根据继承的方式,相应地成为了子类的保护成员或私有成员,而基类的私有成员仍为基类的私有成员,子类无法访问基类的私有成员。
继承
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
对基类的继承方式有三种:共有继承、私有继承、保护继承。
(1)共有继承:基类的共有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有;
(2)私有继承:基类的共有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类私有;
(3)保护继承:基类的共有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。
ps:“其私有成员仍为基类私有”的意思是,基类的私有成员并没有成为派生类的私有成员,它仍然是基类的私有成员,只有基类的成员函数才能访问它,而不能被派生类的成员函数访问
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。
多态
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现接口重用。在C++中, 多态性体现在具有不同功能的函数可以使用同一个函数名,这样就可以通过一个函数名调用不同的函数内容。
实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
关于虚函数,最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。
比如下面的例子:
#include <iostream>using namespace std;class person
{public: void sleep()
{ cout<<"person sleep"<<endl;
} virtual void get_up()
{ cout<<"person get up"<<endl;
}
};class student:public person
{public: void sleep()
{ cout<<"student sleep"<<endl;
} void get_up()
{ cout<<"student get up"<<endl;
}
};void main()
{
student ming;
person *ptr;
ptr = &ming;
ptr->sleep();
ptr->get_up();
ming.sleep ();
ming.get_up ();
}123456789101112131415161718192021222324252627282930313233343536123456789101112131415161718192021222324252627282930313233343536
运行结果为:
person sleep
student get up
student sleep
student get up12341234
同样,如果将一个子类的指针指向一个基类的对象,也可以调用相应的虚函数。只是没有使用虚函数,则调用子类的函数。如上面定义的类,若main函数为:
person we;student *ptr;ptr = (student *)&we;ptr->sleep();ptr->get_up();1234512345
则结果为:
student sleep
person get up
1、数据类型
C++标准只规定每种数据类型的最大值、最小值。至于用二进制编码表示,还是以BCD码表示并未规定,所以,对于不同的编译器,不同的造作系统(如:32位的以及64位的操作系统),每种数据类型所占用的字节数不一定相同。
Float为单精度浮点数,double为双精度浮点数。说明符用于修改数据类型具有的最大值、最小值。说明符有short,long, signed, unsigned。
2、指针的作用
设计指针时的一个很大动机就是在函数内部通过指针改变外部对象。在C++中除了指针又引进了一种新的对于地址的使用方式,即为引用。引用为对固定地址的传递,可以认为具有常量指针的某些特征—不能改变所指向的地址。使用引用使得对地址的传递更加方便。
指针的类型中的Void类型的使用比较特殊。Void类型的指针允许指向任何类型的数据类型。所以,在使用之前(即存取指向的地址位置的内容之前)必须对其进行强制的类型转换,增强了程序的灵活性。但是,Void类型的指针还是不提倡使用。因为,它可以将一种类型转化为另一种类型,所以,可能带来程序的奔溃。
3、变量的作用域
变量的作用域为从变量定义起始行到与定义该变量前一个最近一个开括号配对的第一个闭括号。
4、静态变量
静态变量同全局变量有一定的相似之处,他们都存在于整个生命周期,而不像局部变量只是在进入作用域时自动生成,离开作用域时自动消失。所以,同样可以用静态变量记录一些函数调用之间的片段信息。静态变量只在第一次调用时执行,函数调用之间变量的值保持不变。但是,静态变量的作用域范围则与全局变量不同,静态变量在其范围之外是不可用的,所以,可以使它不被外部的程序所破坏。当静态变量用于所有函数名和所有函数外部的变量时,它会将作用域限定在此文件范围以内。由于静态变量的作用域最多为文件域,所以,它不能与外部变量extern同时用于描述某一变量的特性。
5、外部变量
用于使用外部文件的一个变量,当在一个文件中看到extern的外部变量时,编译器即知道,在别的某个文件中或者本文件的后面存在在这样的一个变量的定义。
6、连接
有内部连接和外部连接。内部连接只对正在编译的的文件创建单独存储空间。用内部连接,别的文件可以使用相同的标识符或者全局变量,连接器不会发生冲突。
外部连接为所有编译过的文件建立一个单独的存储空间。通过用关键字extern声明,可以从别的文件访问此变量和函数。C++中默认将函数和全局变量定义为外部连接。
这里先介绍一下自由函数:如果一个函数为自由函数,那么该函数即不是类的成员函数也不是友元函数。
C++中有内部连接情况如下:
命名空间中的静态变量、静态自由函数、静态友元函数、常量的定义
Enum定义
Inline函数定义
声明
类的定义(同时,类的成员函数、变量在类的内部的声明永远都有内部连接)
Union的定义
友元函数为全局作用域,但是为内部连接属性。
C++中有外部连接的情况如下:
命名空间中的非静态变量、非静态自由函数、非友元函数
类中的静态成员变量
类的非inline成员函数的定义(即成员函数不是在类的内部定义,是在将成员函数的定义域声明分开)全都为外部连接
Main()为全局自由函数为外部连接,所以,不能有两个main()函数
内联函数总有内部连接。所以,类的内联成员函数的定义,最好放在定义本类的头文件中。若放到别的文件中在连接的时候就会产生找不到外部连接的错误。
7、常量
C++中可以使用const来定义某一个对象为常量,这一点与C是相同的。但是,他的作用域与C中的确大大不同。C++中如果用const定义某对象除了限制该对象不能被改变之外同普通变量一样有着自己对应的作用域。但是,C中却不是,在C中会为const创建存储空间,所以,如果在多个文件中定义相同名字的const常量则编译器将会报错0。在C++中一般将const定义放在头文件中,这样当有文件对它使用时,该文件只要包含此头文件。从而将const常量引入此文件,而此时编译器并不const常量分配存储空间。如果,一个常量被别的文件显式的用extern关键字所引用时,为了能被多个文件所使用,此时编译器必须要为常量分配空间,但是,一旦为此常量分配空间就意味着编译器此时不能知道此常量的值了(此时值存放在内存中),所以,也就不能使用常量折叠了。
由于const不能完全避免内存分配,所以,const默认为内部连接,从而将const常量域限制在文件内,这样避免被多个文件所引用,而致使在多个文件中分配内存,造成重复的分配内存。这样文件域中的const变量就可以在文件中用常量折叠。即使在单文件中const常量被迫分配了存储空间,但是由于const为内部连接属性,编译器知道常量值被保存到程序的某个地方,也知道const常量的值是不会改变的,所以,常量的值仍然是有效的,所以,仍然可以在此文件域中使用常量表达式以及常量折叠。
常量折叠:即编译时已经获得了这个变量的值(常量),这样就可以用这个常量去定义一些数组的大小等。如:int a[MAX];MAX为一个常量。
8、预处理宏 (加群:590750544 学习c语言c++游戏开发——群里有大量游戏开发教学视频)
宏可以方便程序参数的修改,节省输入时间。所调宏的地方预处理器都将用宏的定义所替换。但是,使用宏也有一定的风险——C++编译器对所有使用宏的地方不进行数据类型的检查。
9、运算符
位运算符,处理一个数中的个别位(因为浮点数采用一种特殊的内部格式,所以,运算符只适用于整型char int long)。
移位运算符,右移为<<,左移>>。移位时要注意是逻辑移位还是算数移位。
9、C++中的数据类型的转换
对一般的对象进行类型转换不会出现问题。但是对指针的类型的转化要特别的小心,如将指向一个占用存储空间小的变量转化为一个比他大的存储空间时,可能会破坏别的数据。
显式的类型转化:static_cast,用于良性、适度良性转换。包括编译器允许的做的,不用强制转换的“安全转换”以及不太安全但是清楚定义的变换。Const_cast用于对const和volatile的转换。Reinterpret_cast,就是当用的时候却发现,所得到的东西已经不同了,以至于他不能被用于类型原来的目的,除非把他转换回来,这个是最不安全的转换机制。
10、asm关键字、typedef关键字、struct关键字、枚举
Asm关键字用于在C++中嵌入汇编语言,用于高校调整以及特殊的处理器命令。Typedef,类型别名,用于节省输入,但大量使用会导致程序的可读性变差。Struct将基本的数据类型组合成复杂的数据类型。C++中的struct创建结构体时默认已经加入了typedef。所以,可以将struct所定义的数据结构当做基本的数据类型一样使用。结构体变量、以及结构体数组的使用和一般数据类型的变量和数组的使用方式相同,地址的取地址的方式也相同。&Stru取一个结构体变量的地址。&str[i]去一个结构体数组对象中某一个的地址。
枚举,即是将标识符与一整数联系起来,方便程序的阅读,但是由于存在不安全的类型转换。所以,在C++中很少使用。
11、union
使用联合的目的是减少内存的浪费,可以用一个变量来存放不同的数据类型,而联合的大小为联合中最大的数据类型的大小。在联合中存放数据时都是存储在联合的起始位置。所以,同一时间联合只能存放一个数据。
12、C++中的程序运行时的命令行参数
Int main(int argc , char * argv[]),其中argc用于记录命令行参数传递过来多少个参数,默认会将本程序的名字和路径放到第一个参数的位置,即argv[0]处。
此处提一下,在标准的C库的<cstdilib>中提供了atoi(),atol以及atof()。可以方便的将ASCII码很方便的转化为int,long,double浮点值。宏定义时,可以用“#参数”预处理命令将这参数转化为一个字符串。
13、指针的算数运算
指针会根据所指向的数据类型而动态的选择相应算术运算的基本单位。如int,double,float等。但是,两个指针的相加是没有意义的,编译器也不允许这样干。
14、C++中的main()函数的参数
Main函数有两个参数,int main(int agrc , char * argv[])第一个参数agrc为后面参数数组的大小。第二个参数中存放着由命令行输入的参数,默认数组的0号下标存放此执行程序的路径及名称。
15、调试技巧
调试标记,可以使用#define定义一个或者多个标记。根据这些标记可以测试一个使用
#ifdef
code
#endif
所限定的测试代码(code)。
Assert(argumnet)宏,也可以在代码中使用该宏来测试程序运行到本assert()时是否正常(argument(断言)是否预料的值)。如果正常则继续运行,否则发出一个错误消息,并告诉断言是什么以及失败之后将停止程序的运行。使用assert()断言需要包含C++语言cassert标准头文件。调试完成后在程序的#include<cassert>之前插入#define NDEBUG,就可以消除红产生的代码。
16、函数地址
函数在载入计算机时会占用一定的地址,所以,函数也有地址,也可以定义一个函数指针来指向某一个函数。如:void (*funcPtr)();为定义了一个指向无返回值,无参数的函数指针。定义函数指针时采用“从中间开始,向两边扩张,先右后左”的原则。如:
void *(*(*fp1)(int))[10]
表示为:fp1是什么——fp1是一个函数(右)——fp1是一个指向函数的指针(该函数有个int型的参数)(左)——fp1是一个指向函数的指针(该函数有个int型的参数)。该函数返回一个数组,数组的大小为10(右)——fp1是一个指向函数的指针。该函数返回一个指针数组,指针数组中的指针的类型为void类型,指针数组的大小为10。(左)。再如,
int (*(*f4())[10])()
为定义一个函数:f4是一个指向函数的指针,该函数返回一个大小为10的指针数组,指针数组中的指针指向一个函数,该函数返回一个int型值。如同数组的名字代表数组名字指向数组的地址一样,函数的名字也指向函数的地址。
17、指针常量和常量指针
指针常量:指针是一个变量,所以,它可以指向不同的常量,但是它指向的是一个常量,即它指向的地址不能存放别的值。
常量指针:指针是一个常量,它只能指向一个地址。而该地址是一个变量可以存放不同的值。
用法:常量指针:const * ptr,指针常量:* const prt
18、volatie关键字
用volatile关键字告诉编译器,这个变量是一个特殊地址。这个地址的内容可能被某些编译器未知的因素所修改。如:操作系统,多线程,汇编代码、硬件所改变。所以,告诉编译器不要优化该变量(每次对该变量的访问都从改变所在的地址访问,而非优化过的寄存器等)。来保持数据的一致性、有效性。
(加群:590750544 学习c语言c++游戏开发——群里有大量游戏开发教学视频)
此文为个人学习C++编程思想时个人心得体会,所以,难免有不严谨之处,甚至有可能是错误的理解。如发现错误,或者疑问。欢迎提出讨论,共同学习、进步