QT_C++_语言新特性
C++的新特性
C++比 C 语言新增的数据类型是布尔类型(bool)。但是在新的 C 语言标准里已经有布尔类型了,但是在旧的 C 语言标准里是没有布尔类型的,编译器也无法解释布尔类型。在传统的 C 语言里,变量初始化时必须在程序的前面定义在前面,而 C++则是可以随用随定义。C++也可以直接初始化,比如 int x(100);这样就直接赋值 x=100,这些都是 C++特性的好处。这里只说这些常用的新特性,其他特性不做描述或者解释了。
C++的输入输出方式
//正确实例
cout<<x<<endl;
//x 可以是任意数据类型,甚至可以写成一个表达式,这比 C 语言需要指定数据类型方便多了,endl 指的是换行符,与 C 语言的“\n”效果一样
//错误实例
cout<<x,y<<endl; // 在变量间不能用“,”。
//正确实例
cout<<x<<y; // endl 可流省略,只是一个换行的效果。
//cin 语法形式
cin>>x;
//x 可以是任意数据类型。
//如何输入两个不同的变量。
cin>>x>>y;
C++之命名空间 namespace
说明:using 是编译指令,声明当前命名空间的关键词。可以从字面上理解它的意思,using 翻译成使用。这样可以理解成使用命名空间 std。因为 cin 和 cout 都是属于 std 命名空间下的东西,所以使用时必须加上 using namespace std;这句话。cin 和 cout 可以写 std::cin 和 std::cout,“::”表示作用域,cin 和 cout 是属于 std 命名空间下的东西,这里可以理解成 std 的 cin 和 std 的 cout。
//要注意第 1 行,不能写成 iostream.h,有.h 的是非标准的输入输出流,c 的标准库。无.h 的是标准输入输出流就要用命名空间。
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, World!" << endl;
return 0;
}
为什么要使用命名空间?
有些名字容易冲突,所以会使用命名空间的方式进行区分,具体来说就是加个前缀。比如 C++ 标准库里面定义了 vector 容器,您自己也写了个 vector 类,这样名字就冲突了。于是标 准库里的名字都加上 std:: 的前缀,您必须用std::vector 来引用。同理,您自己的类也可以加 个自定义的前缀。但是经常写全名会很繁琐,所以在没有冲突的情况下您可以偷懒,写一句
using namespace std;,接下去的代码就可以不用写前缀直接写 vector 了。 从命名空间开始我们就隐隐约约可以看到
C++面向对象的影子了。命名空间在很多 C++库 里使用到。有些公司也会自定义自己的 C++库,里面使用了大量的命名空间。从这里我们也可以看出 C++是非常之有条理的,容易管理的,不含糊,易使用的。 在初学 Qt时我们是比较少使用命名空间,或者比较少看到命名空间。当然也是可以在 Qt 里自定义命名空间,然后与 C++一样正常使用。
#include <iostream>
using namespace std;
namespace A
{
int x = 1;
void fun()
{
cout<<"A namespace"<<endl;
}
}
using namespace A;
int main()
{
fun(); //行声明了命名空间 A 后,才能直接使用 fun();否则要写成 A::fun();
A::x = 3;
cout<<A::x<<endl;
A::fun();
return 0;
}
C++面向对象
面向对象的三大特征是继承,多态和封装类和对象
#include <iostream>
#include <string>
using namespace std;
class Dog //定义了一个 Dog 狗,定义类时让人一看就知道什么意思
{
public:
string name;
int age;//访问限定符 public(公有的),此外还有 private(私有的)和 protected(受保护的)。写这个的目的是为了下面我们要调用这些成员,不写访问限定符默认是 private。
void run()
{
cout<<"小狗的名字是:"<<name<<","<<"年龄是"<<age<<endl;
}
};
int main()
{
Dog dog1; //从栈中实例化一个对象 dog1(可以随意起名字)
dog1.name = "旺财";//为 dog1 的成员变量赋值,dog1 的 name 赋值叫“旺财”
dog1.age = 2;
dog1.run();
Dog *dog2 = new Dog();//从堆中实例化对象,使用关键字 new 的都是从堆中实例化对象。
if (NULL == dog2) //从堆中实例化对象需要开辟内存,指针会指向那个内存,如果 new 没有申请内 存成功,p 即指向 NULL,程序就自动退出,下面的就不执行了,写这个是为了严谨。
{
return 0;
}
dog2->name = "富贵";//和 dog1 一样,为 dog2 的成员赋值
dog2->age = 1;
dog2->run();
delete dog2; //释放内存,将 dog2 重新指向 NULL
dog2 = NULL;
return 0;
}
构造函数与析构函数
构造函数的特点如下:
(1) 构造函数必须与类名同名;
(2) 可以重载,(重载?新概念,后面学到什么是重载。);
(3) 没有返回类型,即使是 void 也不行。
析构函数的特点如下:
(1) 析构函数的格式为~类名();
(2) 调用时释放内存(资源);
(3) ~类名()不能加参数;
(4) 没有返回值,即使是 void 也不行。
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
Dog();//构造函数
~Dog();//析构函数
};
int main()
{
Dog dog;
cout<<"构造与析构函数示例"<<endl;
return 0;
}
Dog::Dog()//类的函数可以在类里实现,也可以在类外实现,不过在类外实现时需要使用“::”
{
cout<<"构造函数执行!"<<endl;
}
Dog::~Dog()//类的函数可以在类里实现,也可以在类外实现,不过在类外实现时需要使用“::”
{
cout<<"析构函数执行!"<<endl;
}
this 指针
一个类中的不同对象在调用自己的成员函数时,其实它们调用的是同一段函数代码,那么
成员函数如何知道要访问哪个对象的数据成员呢?
没错,就是通过 this 指针。每个对象都拥有一个 this 指针,this 指针记录对象的内存地址。
在 C++中,this 指针是指向类自身数据的指针,简单的来说就是指向当前类的当前实例对象。
关于类的 this 指针有以下特点:
(1) this 只能在成员函数中使用,全局函数、静态函数都不能使用 this。实际上,成员函数
默认第一个参数为 T * const this。也就是一个类里面的成员了函数 int func(int p),func 的原
型在编译器看来应该是 int func(T * const this,int p)。 (2) this 在成员函数的开始前构造,在成员函数的结束后清除。 (3) this 指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全
局变量。
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
void func();
};
int main()
{
Dog dog;
dog.func();
return 0;
}
void Dog::func()
{
this->name = "旺财";
cout<<"小狗的名字叫:"<<this->name<<endl;
}
继承
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了
一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。在 Qt 里大量的使用
了这种特性,当 Qt 里的类不满足自己的要求时,我们可以重写这个类,就是通过继承需要重写
的类,来实现自己的类的功能。
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生
类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
与类的访问修饰限定符一样,继承的方式也有几种。其中,访问修饰符 access-specifier 是
public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使
用访问修饰符 access-specifier,则默认为 private。
下面来捋一捋继承的方式,例子都是以公有成员和公有继承来说明,其他访问修饰符和其
他继承方式,大家可以在教程外自己捋一捋。这个公有成员和继承方式也没有什么特别的,无
非就是不同的访问权限而已,可以这样简单的理解。
1、公有继承(public):当一个类派生继承公有基类时,基类的公有成员也是派生类的公有成
员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但
是可以通过调用基类的公有和保护成员来访问。
2、保护继承(protected): 当一个类派生继承保护基类时,基类的公有和保护成员将成为派
生类的保护成员。
3、私有继承(private):当一个类派生继承私有基类时,基类的公有和保护成员将成为派生类
的私有成员。
#include <iostream>
#include <string>
using namespace std;
/*动物类,抽象出下面两种属性,*颜色和体重,是每种动物都具有的属性*/
class Animal
{
public:
/* 颜色成员变量 */
string color;
/* 体重成员变量 */
int weight;
};
/*让狗类继承这个动物类,并在狗类里写自己的属性。*狗类拥有自己的属性 name,age,run()方法,同时也继承了*动物类的 color 和 weight 的属性
*/
class Dog : public Animal//Animal 作为基类,Dog 作为派生类。Dog 继承了 Animal 类。访问修饰符为 public(公有继承)。
{
public:
string name;
int age;
void run();
};
int main()
{
Dog dog;
dog.name = "旺财";
dog.age = 2;
dog.color = "黑色";
dog.weight = 120;
cout<<"狗的名字叫:"<<dog.name<<endl;
cout<<"狗的年龄是:"<<dog.age<<endl;
cout<<"狗的毛发颜色是:"<<dog.color<<endl;
cout<<"狗的体重是:"<<dog.weight<<endl;
return 0;
}
重载
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符
重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但
是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参
数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为
重载决策。
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
void getWeight(int weight)
{
cout<<name<<"的体重是:"<<weight<<"kG"<<endl;
}
void getWeight(double weight)
{
cout<<name<<"的体重是:"<<weight<<"kG"<<endl;
}
};
int main()
{
Dog dog;
dog.name = "旺财";
dog.getWeight(10);
dog.getWeight(10.5);
return 0;
}
多态
C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;
形成多态必须具备三个条件:
1、 必须存在继承关系;
2、继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字 virtual 声明的函数,在派
生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
3、存在基类类型的指针或者引用,通过该指针或引用调用虚函数。
虚函数:
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,
会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类
型来选择调用的函数,这种操作被称为动态链接,或后期绑定。虚函数声明如下:virtual
ReturnType FunctionName(Parameter) 虚函数必须实现,如果不实现,编译器将报错
纯虚函数:
若在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基
类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。纯虚函数声明如下:
virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。
包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的
指针或引用。
#include <iostream>
#include <string>
using namespace std;
/* 定义一个动物类 */
class Animal
{
public:
virtual void run()
{
cout<<"Animal 的 run()方法"<<endl;
}
};
/* 定义一个狗类,并继承动物类 */
class Dog : public Animal
{
public:
void run()
{
cout<<"Dog 的 run()方法"<<endl;
}
};
/* 定义一个猫类,并继承动物类 */
class Cat : public Animal
{
public:
void run()
{
cout<<"Cat 的 run()方法"<<endl;
}
};
int main()
{
/* 声明一个 Animal 的指针对象,注:并没有实例化 */
Animal *animal;
/* 实例化 dog 对象 */
Dog dog;
/* 实例化 cat 对象 */
Cat cat;
/* 存储 dog 对象的地址 */
animal = &dog;
/* 调用 run()方法 */
animal->run();
/* 存储 cat 对象的地址 */
animal = &cat;
/* 调用 run()方法 */
animal->run();
return 0;
}
多态不好理解,这边再增加一下解释和举例
注意:
1、通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。
2、有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。
3、C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。
4、派生类比较多,如果不使用多态,那么就需要定义多个指针变量,很容易造成混乱;而有了多态,只需要一个指针变量 p 就可以调用所有派生类的虚函数。
#include <iostream>
using namespace std;
//军队
class Troops{
public:
virtual void fight(){ cout<<"Strike back!"<<endl; }
};
//陆军
class Army: public Troops{
public:
void fight(){ cout<<"--Army is fighting!"<<endl; }
};
//99A主战坦克
class _99A: public Army{
public:
void fight(){ cout<<"----99A(Tank) is fighting!"<<endl; }
};
//武直10武装直升机
class WZ_10: public Army{
public:
void fight(){ cout<<"----WZ-10(Helicopter) is fighting!"<<endl; }
};
//长剑10巡航导弹
class CJ_10: public Army{
public:
void fight(){ cout<<"----CJ-10(Missile) is fighting!"<<endl; }
};
//空军
class AirForce: public Troops{
public:
void fight(){ cout<<"--AirForce is fighting!"<<endl; }
};
//J-20隐形歼击机
class J_20: public AirForce{
public:
void fight(){ cout<<"----J-20(Fighter Plane) is fighting!"<<endl; }
};
//CH5无人机
class CH_5: public AirForce{
public:
void fight(){ cout<<"----CH-5(UAV) is fighting!"<<endl; }
};
//轰6K轰炸机
class H_6K: public AirForce{
public:
void fight(){ cout<<"----H-6K(Bomber) is fighting!"<<endl; }
};
int main(){
Troops *p = new Troops;
p ->fight();
//陆军
p = new Army;
p ->fight();
p = new _99A;
p -> fight();
p = new WZ_10;
p -> fight();
p = new CJ_10;
p -> fight();
//空军
p = new AirForce;
p -> fight();
p = new J_20;
p -> fight();
p = new CH_5;
p -> fight();
p = new H_6K;
p -> fight();
return 0;
}
//运行结果如下
Strike back!
--Army is fighting!
----99A(Tank) is fighting!
----WZ-10(Helicopter) is fighting!
----CJ-10(Missile) is fighting!
--AirForce is fighting!
----J-20(Fighter Plane) is fighting!
----CH-5(UAV) is fighting!
----H-6K(Bomber) is fighting!
数据封装
注意:在构造函数里初始化 total 的数量,不初始化 total 的数量默认是随 int 类型的数。所以我们需要在构造函数里初始化,也体现了构造函数的功能,一般是在构造函数里初始化。不要在类内直接赋值初始化,有可能有些编译器不支持。
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
Dog(int i = 0)
{
total = i;
}
void addFood(int number) //将获得的食物份数赋值给 total。
{
total = total + number;
}
int getFood() //通过调用这个方法,即可访问私有成员的 total 总数。
{
return total;
}
private:
int total;
};
int main()
{
Dog dog;
dog.name = "旺财";
dog.addFood(3);
dog.addFood(2);
cout<<dog.name<<"总共获得了"<<dog.getFood()<<"份食物"<<endl;
return 0;
}
数据抽象
数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
纯虚函数语法格式为:
virtual 返回值类型 函数名 (函数参数) = 0;
1、纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。
2、最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。
3、包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。
4、抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。
5、 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。以下代码说明:
本例中定义了四个类,它们的继承关系为:Line --> Rec --> Cuboid --> Cube。
Line 是一个抽象类,也是最顶层的基类,在 Line 类中定义了两个纯虚函数 area() 和 volume()。
在 Rec 类中,实现了 area() 函数;所谓实现,就是定义了纯虚函数的函数体。但这时 Rec 仍不能被实例化,因为它没有实现继承来的 volume() 函数,volume() 仍然是纯虚函数,所以 Rec 也仍然是抽象类。
直到 Cuboid 类,才实现了 volume() 函数,才是一个完整的类,才可以被实例化。
可以发现,Line 类表示“线”,没有面积和体积,但它仍然定义了 area() 和 volume() 两个纯虚函数。这样的用意很明显:Line 类不需要被实例化,但是它为派生类提供了“约束条件”,派生类必须要实现这两个函数,完成计算面积和体积的功能,否则就不能实例化。
在实际开发中,你可以定义一个抽象基类,只完成部分功能,未完成的功能交给派生类去实现(谁派生谁实现)。这部分未完成的功能,往往是基类不需要的,或者在基类中无法实现的。虽然抽象基类没有完成,但是却强制要求派生类完成,这就是抽象基类的“霸王条款”。
抽象基类除了约束派生类的功能,还可以实现多态。请注意第 51 行代码,指针 p 的类型是 Line,但是它却可以访问派生类中的 area() 和 volume() 函数,正是由于在 Line 类中将这两个函数定义为纯虚函数;如果不这样做,51 行后面的代码都是错误的。我想,这或许才是C++提供纯虚函数的主要目的。
#include <iostream>
using namespace std;
//线
class Line{
public:
Line(float len);
virtual float area() = 0;
virtual float volume() = 0;
protected:
float m_len;
};
Line::Line(float len): m_len(len){ }
//矩形
class Rec: public Line{
public:
Rec(float len, float width);
float area();
protected:
float m_width;
};
Rec::Rec(float len, float width): Line(len), m_width(width){ }
float Rec::area(){ return m_len * m_width; }
//长方体
class Cuboid: public Rec{
public:
Cuboid(float len, float width, float height);
float area();
float volume();
protected:
float m_height;
};
Cuboid::Cuboid(float len, float width, float height): Rec(len, width), m_height(height){ }
float Cuboid::area(){ return 2 * ( m_len*m_width + m_len*m_height + m_width*m_height); }
float Cuboid::volume(){ return m_len * m_width * m_height; }
//正方体
class Cube: public Cuboid{
public:
Cube(float len);
float area();
float volume();
};
Cube::Cube(float len): Cuboid(len, len, len){ }
float Cube::area(){ return 6 * m_len * m_len; }
float Cube::volume(){ return m_len * m_len * m_len; }
int main(){
Line *p = new Cuboid(10, 20, 30);
cout<<"The area of Cuboid is "<<p->area()<<endl;
cout<<"The volume of Cuboid is "<<p->volume()<<endl;
p = new Cube(15);
cout<<"The area of Cube is "<<p->area()<<endl;
cout<<"The volume of Cube is "<<p->volume()<<endl;
return 0;
}