1,C++入门
- 一个C++程序由一个或多个编译单元(compilation unit)构成。每个编译单元都是一个独立的源代码文件,通常是一个带.cpp扩展名(其他常用的扩展名还有.cc和.cxx)的文件,编译器每次可以处理一个这样的文件。对于每一个编译单元,编译器都会产生一个目标文件,它的扩展名是.obj(在Windows中)或者.o(在UNIX和Mac OS X中)。这个目标文件是一个二进制文件,其中包含了系统架构方面的机器代码,而程序则要运行在此基础之上。
- 一旦所有的.cpp都已编译完成,那么我们就可以使用一个称为连接器的特殊程序,把这些目标文件连接在一起,生成一个可执行程序。连接器会连接这些目标文件,并且会解析函数和编译单元中应用到的其他符号的内存地址。
- 在构建一个程序时,必须确保其中的某个编译单元包含一个main()函数,它是程序入口的标志。这个函数不属于任何类,它是一个全局函数(global function)。(编译-连接-生成 可执行文件)
- 不像Java的每一个源文件都必须包含一个类那样,C++可以按我们想要的形式组织各个编译单元。我们可以在同一个.cpp文件中实现多个类,或者也可以把一个类的实现分散到多个.cpp文件中,并且还可以把这些源文件命名为我们所喜欢的任意名字。当在某一个特殊的.cpp文件中进行修改时,只需要重新编译那个文件,然后再重新连接这个应用程序就可以生成一个新的可执行程序。
源文件main.cpp包含了main()函数的定义。在C++中,这个函数的参数是一个int和一个char* 数组(一个字符串数组)。可以从argv[0]中获取程序的名字,命令行参数则分别放在argv[1],argv[2],...argv[argc-1]中。把参数命名为argc(argument count,参数个数)和argv(argument values,参数值)是一种习惯性的做法。如果这个程序不能使用命令行参数,那么可以把main()定义成不带参数的形式。
#include<cstdlib>
#include<iostream>
标准的头文件没有.后缀。包围文件名的尖括号说明这些头文件都位于系统的标准位置,而双引号则告诉编译器要到当前目录中查找头文件。
库主要有两种类型:
- 静态库(static library)可以直接放进可执行程序,就好像它们也是一些目标文件一样。这可以确保不会弄丢这些库,但却会让可执行程序变得很大。
- 动态库(dynamic library,也称共享库或dll)位于用户机器上的标准位置,并且会在应用程序启动的时候自动加载它们。
2,主要语言之间的差异
(C++,Java,C#)语言之间的这些许多不同之处都是因为C++的可编译本性和对性能的追求而产生的。因此,C++不会在运行时检测数组是否越界,并且也没有垃圾信息收集器回收那些分配出去但是却不再使用的动态内存。
在使用QT编程时,暂时不需要的一些主题有:模板类和模板函数的定义、共用体类型的定义,以及异常的使用等。
- 基本数据类型:
bool,char,short,int,long,long long,float,double
Qt提供了一个QChar类型,它可用于存储16位的Unicode字符。
内置类型的实例不会被默认初始化。当创建一个int变量时,它的值应当可以明确地说是0,但是也很有可能是-209486515。幸运的是,绝大多数的编译器都会在我们试图读取未初始化变量时给予警告,并且我们也可以使用一些像Rational PurifyPlus和Valgrind这样的工具来检查运行时对未初始化内存的访问和其他与内存相关的问题。
point2d.h
#ifndef POINT2D_H
#define POINT2D_H
class point2d
{
public:
point2d();
point2d(double x,double y);
void setX(double x);
void setY(double y);
double x();
double y();
private:
double xVal;
double yVal;
};
#endif // POINT2D_H
point2d.cpp
#include "point2d.h"
point2d::point2d()
{
xVal = 0.0;
yVal = 0.0;
}
point2d::point2d(double x,double y)
{
xVal = x;
yVal = y;
}
void point2d::setX(double x)
{
xVal = x;
}
void point2d::setY(double y)
{
yVal = y;
}
double point2d::x() const
{
// xVal = xVal + 1;
return xVal;
}
double point2d::y() const
{
// yVal = xVal + 1;
return yVal;
}
main.cpp
#include <QCoreApplication>
#include <QTimer>
#include <qdatetime.h>
#include <QDebug>
#include "point2d.h"
#include <iostream>
using namespace std;
int main(int argc,char *argv[])
{
point2d* point = new point2d(1,2);
cout<<"x:"<<point->x()<<endl;
cout<<"y:"<<point->y()<<endl;
return a.exec();
}
用来获取值的函数x()和y()声明为const。这意味着它们不会(而且也不能)修改成员变量或者调用非const成员函数。
在C++中,任意类型的变量都可以直接声明而不必一定要使用new。如下,第一个变量会使用默认的point2d构造函数(这个构造函数没有参数)进行初始化。第二个变量则使用第二个构造函数进行初始化。对一个对象的成员进行访问需要使用"."(点)操作符。
#include "point2d.h"
int main(int argc,char *argv[])
{
point2d alpha;
point2d beta(0.666, 0.875);
alpha.setX(beta.y());
beta.setY(aplha.x());
return 0;
}
作为一种面向对象的语言,C++支持继承(inheritance)和多态(polymorphism)。为了说明它们是如何工作的,我们将分析一个例子,该例以Shape为抽象基类,以Circle为子类。先从基类开始:
#ifndef SHAPE_H
#define SHAPE_H
#include "point2d.h"
class Shape
{
public:
Shape(point2d center){myCenter = center;}
virtual void draw() = 0;
protected:
point2d myCenter;
};
#endif // SHAPE_H
这个定义放在头文件shape.h中。由于在这个类的定义引用了类Point2D,所以需要包含头文件point2d.h。
类Sharp没有基类。这一点不像Java和C#,C++没有为所有的类提供一个可以从中继承出来的一般类Object。Qt则为所有类型的对象提供了一个简单基类QObject。
draw()函数的声明有两个有趣的特点:它含有virtual 关键字,并且以 “=0” 为结尾。关键字 virtual 表明这个函数可能会在子类中重新得到实现。就像在C#中一样,C++的成员函数在默认情况下也是不能重新实现的。这个奇特的 “=0” 的语句表明这个函数是一个纯虚函数(pure virtual function)------ 一个没有默认实现代码并且必须在子类中实现的函数。要把Java和C#中的“接口”的概念对应到类中,就只能用C++的纯虚函数来表示了。
以下是子类Circle的定义:
#ifndef CIRCLE_H
#define CIRCLE_H
#include "Shape.h"
class Circle:public Shape
{
public:
Circle(point2d center,double radius = 0.5):Shape(center)
{
myRadius = radius;
}
void draw()
{
//do something
}
private:
double myRadius;
};
#endif // CIRCLE_H
类Circle通过公有(public)方式继承了Shape,也就是说,Shape中的所有成员在Circle中仍旧是公有的。C++也支持保护(protected)继承和私有(private)继承,利用它们可以限制对基类public基类的public成员和protected成员的访问。
这个构造函数带有两个参数。第二个参数是可选的,并且如果没有给定参数值就会取0.5。构造函数在函数名和函数体之间使用一种特殊的语法把center参数传递给基类的构造函数。在函数体中,我们对成员变量myRadius进行了初始化。在基类构造函数初始化时,我们也本应在同一行初始化该变量:
Circle(point2D center,double radius = 0.5):Shape(center),myRadius(radius){ }
另一方面,C++不允许在类定义中初始化成员变量,因此下面的代码是错误的:
private:
double myRadius = 0.5;
draw()函数Shape中声明的虚函数draw()具有相同的名字。它是该函数的一个重新实现,并且在Circle实例上通过Shape引用或者指针调用draw()时,就会以多态的形式调用该函数。C++不像C#那样有override关键字。而且C++也没有能够指向基类的super或者base关键字。如果需要调用一个函数的基本实现,则可以在这个函数的名字前加上一个由基类的名字和‘::’操作符构成的前缀。例如:
class LabeledCircle: public Circle
{
public:
void draw()
{
Circle::draw();
drawLabel();
}
...
};
C++支持多重继承,也就是说,一个类可以同时从多个类中派生出来。语法形式如下所示:
class DerivedClass:public BaseClass1,public BaseClass2,...,public BaseClassN
{
...
};
默认情况下,类中声明的函数和变量都与这个类的实例相关。我们也可以声明静态(static)成员函数和静态成员变量,可以在没有实例的情况下使用它们。例如:
#ifndef TRUCK_H
#define TRUCK_H
class Truck
{
public:
Truck() { ++counter; }
~Truck() { --counter; }
static int instanceCount() { return counter; }
private:
static int counter;
}
通过这里的静态成员变量counter,我们可以在任何时候知道还存在多少个Truck实例。Truck的构造函数会增加它的值。通过前缀 “~” 识别的析构函数(destructor)可以减少它的值。在C++中, 在静态分配的变量超出作用域或者是在删除一个使用new分配的变量时会自动调用这个析构函数。除了我们还可以在某个特定时刻调用析构函数这一点之外,这都与Java中的finalize()方法相似。
一个静态成员变量在一个类中只有单一的存在实体:这样的变量就是“类变量”(class variable)而不是“实例变量”(instance variable)。每一个静态成员变量都必须定义.cpp文件(但是不能再次重复static关键字)中。例如:
#include "truck.h"
int Truck::counter = 0;
不这样做将会在连接时产生一个“unresolved symbol"(不可解析的符号)的错误信息。只要把类名作为前缀,就可以在该类外面访问这个instanceCount()静态函数。例如:
#include <iostream>
#include "truck.h"
int main()
{
Truck truck1;
Truck truck2;
std::cout<<Truck::instanceCount()<< equals 2<<std::endl;
}
网友评论