Linux-C/C++《C++/1、C++基础》(C++语言特性、面向对象等)

这里主要介绍概念为主,主要介绍 C++与 C 语言中常用的不同点,和一些新的变化。其中不会去说指针、数据类型、变量类型、判断和循环等这些知识,这些和C 语言基本是一样使用的。我们主要学习 C++的面向对象编程,对学习 Qt 有很大的帮助。
1、C++语言新特性
1.1 C++语言新特性
C++比 C 语言新增的数据类型是布尔类型(bool)。但是在新的 C 语言标准里已经有布尔类型了,但是在旧的 C 语言标准里是没有布尔类型的,编译器也无法解释布尔类型。在传统的 C 语言里,变量初始化时必须在程序的前面定义在前面,而 C++则是可以随用随定义。C++也可以直接初始化,比如 int x(100);这样就直接赋值 x=100,这些都是 C++特性的好处。这里只说这些常用的新特性,其他特性不做描述或者解释了。
1.2 C++的输入输出方式
在 C 语言里,我们是这样输入或者输出的。
在 C++里,我们使用以 cin 和 cout 代替了 scanf 和 printf。在输入和输出的流程上是不变的,只是关键字变了,用法也变了。
要说效率上,肯定是 C 语言的 scanf 和 printf 的效率高,但是没有 C++中的 cin 和 cout 使用方便。
C++的 I/O 语法方式如下。
cout 语法形式:
cout<<x<<endl; x 可以是任意数据类型,甚至可以写成一个表达式,这比 C 语言需要指定数据类型方便多了,endl 指的是换行符,与 C 语言的“\n”效果一样。
错误示例:
cout<<x,y<<endl; // 在变量间不能用“,”。
正确写法:
cout<<x<<y; // endl 可流省略,只是一个换行的效果。 cin 语法形式:
cin>>x; x 可以是任意数据类型。
拓展,如何输入两个不同的变量。
cin>>x>>y; 1.3 C++之命名空间 namespace
如下代码第 2 行。using namespace std;同时我们要注意第 1 行,不能写成 iostream.h,有.h 的是非标准的输入输出流,c 的标准库。无.h 的是标准输入输出流就要用命名空间。
#include <iostream>
using namespace std;
int main()
{cout << "Hello, World!" << endl;return 0;
} using 是编译指令,声明当前命名空间的关键词。可以从字面上理解它的意思,using 翻译成使用。这样可以理解成使用命名空间 std。因为 cin 和 cout 都是属于 std 命名空间下的东西,所以使用时必须加上 using namespace std;这句话。cin 和 cout 可以写 std::cin 和 std::cout,“::” 表示作用域,cin 和 cout 是属于 std 命名空间下的东西,这里可以理解成 std 的 cin 和 std 的 cout。
为什么要使用命名空间?
有些名字容易冲突,所以会使用命名空间的方式进行区分,具体来说就是加个前缀。比如C++ 标准库里面定义了 vector 容器,您自己也写了个 vector 类,这样名字就冲突了。于是标准库里的名字都加上 std:: 的前缀,您必须用 std::vector 来引用。同理,您自己的类也可以加个自定义的前缀。但是经常写全名会很繁琐,所以在没有冲突的情况下您可以偷懒,写一句using namespace std;,接下去的代码就可以不用写前缀直接写 vector 了。
从命名空间开始我们就隐隐约约可以看到 C++面向对象的影子了。命名空间在很多 C++库里使用到。有些公司也会自定义自己的 C++库,里面使用了大量的命名空间。从这里我们也可以看出 C++是非常之有条理的,容易管理的,不含糊,易使用的。
在初学 Qt 时我们是比较少使用命名空间,或者比较少看到命名空间。当然也是可以在 Qt里自定义命名空间,然后与 C++一样正常使用。
下面通过一个简单的例子来介绍自定义的命名空间和使用自定义的命名空间。在 Ubuntu上我们新建一个目录 02_namespace_example,然后在 02_namespace_example 里新建一个
02_namespace_example.cpp 文件,内容如下。
#include <iostream>
using namespace std;namespace A
{int x = 1;void fun() {cout<<"A namespace"<<endl;}
}
using namespace A;
int main()
{fun();A::x = 3;cout<<A::x<<endl;A::fun();return 0;
} 第 4 行,自定义了命名空间 A,里面定义了一个变量 x,并将 x 赋值为 1;定义了一个函数fun(),并在 fun()加了输出打印语句 cout<<"A namespace"<<endl;。
第 11 行,声明使用命名空间 A。
第 14 行,在第 11 行声明了命名空间 A 后,才能直接使用 fun();否则要写成 A::fun();
第 15 行,将 A 命名空间下的 x 重新赋值为 3。
第 16 行,打印出 A 命名空间下的 x 的值。
第 17 行,调用 A 命名空间下的 fun()。
执行下面的指令开始编译。
g++ 02_namespace_example.cpp -o 02_namespace_example 2、C++面向对象
2.1 类和对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++的核心特性,通常被称为用户定义的类型。类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
打个比方说明一下什么是类,比如有一条小狗,小狗有名字叫旺财,旺财的年龄是 2 岁,同时旺财会汪汪的叫,也能跑。我们统称狗这个为类,类是我们抽象出来的,因为狗不只有上面的属性,还有体重,毛发的颜色等等,我们只抽象出几种属性成一个类。具体到哪条狗就叫对象。
从类中实例化对象分两种方法,一种是从栈中实例化对象,一种是从堆中实例化对象。
下面以自定义狗类介绍如何自定义类和如何使用对象。
在 Ubuntu 上编辑一个 03_class_dog_example 目录,在 03_class_dog_example 目录下新建一个 03_class_dog_example.cpp 文件,内容如下。
#include <iostream>
#include <string>
using namespace std;class Dog
{public:string name;int age;void run() {cout<<"小狗的名字是:"<<name<<","<<"年龄是"<<age<<endl;}
};int main()
{Dog dog1;dog1.name = "旺财";dog1.age = 2;dog1.run();Dog *dog2 = new Dog();if (NULL == dog2) {return 0;}dog2->name = "富贵";dog2->age = 1;dog2->run();delete dog2;dog2 = NULL;return 0;
} 第 5 行,定义了一个 Dog 狗,定义类时,起的类名要尽量贴近这个类,让人一看就明白,您这个类是做什么的。
第 7 行,访问限定符 public(公有的),此外还有 private(私有的)和 protected(受保护的)。写这个的目的是为了下面我们要调用这些成员,不写访问限定符默认是 private。关于访问限定符,如果是初学者可能会难理解。简单的来说,访问限定符就是设置一个成员变量和成员函数的访问权限而已,初学者暂时不必要深究什么时候应该用 public 和什么时候应该用 private。
第 8 至 11 行,定义了一个字符串变量 name,整形变量 age。和一个方法 run()。我们在这个 run()里打印相应的狗名和狗的年龄。PS:string 是 C++的数据类型,方便好用,使用频率相当高。
第 18 行,从栈中实例化一个对象 dog1(可以随意起名字)。
第 20 至 22 行,为 dog1 的成员变量赋值,dog1 的 name 赋值叫“旺财”,年龄为 2 岁。然后调用 run()方法,打印 dog1 的相关变量的信息。
第 24 行,从堆中实例化对象,使用关键字 new 的都是从堆中实例化对象。
第 26 行,从堆中实例化对象需要开辟内存,指针会指向那个内存,如果 new 没有申请内存成功,p 即指向 NULL,程序就自动退出,下面的就不执行了,写这个是为了严谨。
第 29 至 31 行,和 dog1 一样,为 dog2 的成员赋值。
第 34 和 35 行,释放内存,将 dog2 重新指向 NULL。
如果没有语法错误,我们完全可以预测到打印的结果。我们学习 C 语言的结构体,类其实和结构类似,可以说类是结构体的升级版本。
执行下面的指令开始编译。
g++ 03_class_dog_example.cpp -o 03_class_dog_example 通过上面的例子我们已经学习了什么是类,和什么是对象。以描述 Dog 为一类(抽象出来的),从 Dog 类中实例出来就是对象(实际事物)。对象拥有 Dog 类里的属性,可以从栈中实例化对象,亦可从堆中实例化对象。类的编写过程和对象的使用过程大致如上了。我们只需要理解这个步骤,明白类的定义和使用即可。
2.1.1 构造函数与析构函数
什么是构造函数?构造函数在对象实例化时被系统自动调用,仅且调用一次。构造函数出现在哪里?前面我们学过类,实际上定义类时,如果没有定义构造函数和析构函数,编译器就会生成一个构造函数和析构函数,只是这个构造和析构函数什么事情也不做,所以我们不会注意到一点。
构造函数的特点如下:
(1) 构造函数必须与类名同名;
(2) 可以重载,(重载?新概念,后面学到什么是重载。);
(3) 没有返回类型,即使是 void 也不行。
什么是析构函数?与构造函数相反,在对象结束其生命周期时系统自动执行析构函数。实际上定义类时,编译器会生成一个析构函数。
析构函数的特点如下:
(1) 析构函数的格式为~类名();
(2) 调用时释放内存(资源);
(3) ~类名()不能加参数;
(4) 没有返回值,即使是 void 也不行。
下面我们通过简单的例子来说明构造函数和析构函数的使用。新建一个目录04_structor_example,编辑一个 04_structor_example.cpp 内容如下。
#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;
} 2.1.2 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 指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全局变量。
下面以简单的例子来说明 this 的用法。我们还是以狗类为例,按上面的 this 解释,this 只能够在成员函数使用,并可以指向自身数据。我们就可以写这样简单的例子来说明 this 的用法。
我们在 Qt 里也会遇到 this 这个东西,下面这个例子就很容易解释 Qt 里的 this 指针的用法。
新建一个目录 05_this_pointer_example,编辑一个 05_this_pointer_example.cpp 内容如下。
#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;
} 第 21 和 22 行,在类的成员函数里使用了 this 指针,并指向了类里的成员 name。先将 name赋值叫“旺财”,然后我们打印 name 的值。
当程序没有语法错误里我们可以预测打印的结果,就是“小狗的名字叫:旺财”。
2.2 继承
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。在 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):当一个类派生继承私有基类时,基类的公有和保护成员将成为派生类的私有成员。
下面我们还是以狗类为例,在 2.2.1 小节里我们定义的狗类,已经定义了 name,age 和 run() 方法。假设我们不想重写这个狗类,而是新建一个 Animal 类,让狗类去继承这个 Animal 类。假设是公有继承,那么我们是不是可以在狗类实例的对象里去使用继承 Animal 类里的成员呢?带着这个疑问,我们使用下面的例子来说明。
新建一个目录 06_inherit_example,编辑一个 06_inherit_example.cpp 内容如下。
#include <iostream>
#include <string>
using namespace std;/*动物类,抽象出下面两种属性,
*颜色和体重,是每种动物都具有的属性
*/
class Animal
{
public:/* 颜色成员变量 */string color;/* 体重成员变量 */int weight;
};/*让狗类继承这个动物类,并在狗类里写自己的属性。
*狗类拥有自己的属性 name,age,run()方法,同时也继承了
*动物类的 color 和 weight 的属性
*/
class Dog : public Animal
{
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;
} 第 21 行,Animal 作为基类,Dog 作为派生类。Dog 继承了 Animal 类。访问修饰符为 public
(公有继承)。
2.3 重载
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但 是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参 数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
2.3.1 函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指 参数的个数、类型或者顺序)必须不同。我们不能仅通过返回类型的不同来重载函数。在 Qt 源码里,运用了大量的函数重载,所以我们是有必要学习一下什么是函数重载。不仅在 C++, 在其他语言的里,都能看见函数重载。因为需要不同,所以有重载各种各样的函数。
下面通过一个小实例来简单说明一下函数重载的用法。我们还是以狗类为说明,现在假设 有个需求。我们需要打印狗的体重,分别以整数记录旺财的体重和小数记录旺财的体重,同时 以整数打印和小数打印旺财的体重。那么我们可以通过函数重载的方法实现这个简单的功能。
新建一个目录 07_func_overloading,编辑一个 07_func_overloading.cpp 内容如下。
#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;
} 第 9 行,写了一个方法 getWeight(int weight),以 int 类型作为参数。
第 13 行,以相同的函数名 getWeight,不同的参数类型 double weight,这样就构成了函数
重载。
第 22 行与第 23 行,分别传进参数不同的参数,程序就会匹配不同的重载函数。
2.3.2 运算符重载
运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的 C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:operatorp(argument-list),operator 后面的'p'为要重载的运算符符号。重载运算符的格式如下:
<返回类型说明符> operator <运算符符号>(<参数表>)
{<函数体>
} 下面的实例使用成员函数演示了运算符重载的概念。在这里,对象作为参数进行传递,对象 的属性使用 this 运算符进行访问。下面还是以我们熟悉的狗类为例。声明加法运算符用于把两个 Dog 对象相加的体重相加,返回最终的 Dog 对象然后得到第三个 Dog 对象的体重。
新建一个目录 08_operator_example,编辑一个 08_operator_example.cpp 内容如下。
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:int weight;Dog operator+(const Dog &d) {Dog dog;dog.weight = this->weight + d.weight;return dog;}};
int main()
{Dog dog1;Dog dog2;Dog dog3;dog1.weight = 10;dog2.weight = 20;dog3 = dog1 + dog2;cout<<"第三只狗的体重是:"<<dog3.weight<<endl;return 0;
} 第 9 至 13 行,重载“+”运算符,注意函数必须与类名同名,把 Dog 对象作为传递,使用this 运算符进行访问。然后返回一个 dog 对象。
2.4 多态
C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;形成多态必须具备三个条件:
1. 必须存在继承关系;
2. 继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字 virtual 声明的函数,在派 生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
3. 存在基类类型的指针或者引用,通过该指针或引用调用虚函数。这里我们还需要理解两个概念:
虚函数:
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。虚函数声明如下:virtualReturnType FunctionName(Parameter) 虚函数必须实现,如果不实现,编译器将报错
纯虚函数:
若在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。纯虚函数声明如下:virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
上面那些概念大家可以捋一捋,毕竟 C++概念还是挺多的。为什么说到多态要与虚函数和纯虚函数扯上关系?光说概念没有实例确实难理解。下面我们还是以我们熟悉的狗类和动物类,另外加一个猫类进行多态的讲解。
新建一个目录 09_polymorphism_example,编辑一个 09_polymorphism_example.cpp 内容如下。(PS: polymorphism 翻译多态的意思,笔者就以这种方式命名例程了)。
#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;
} 第 9 行、第 18 行和第 28 行,都有一个 run()方法。其中我们可以看到基类 Animal 类的 run()方法前面加了关键字 virtual。这样让基类 Animal 类的 run()方法变成了虚函数。在这个例子里我们可以知道虚函数是 C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。简单的来说,上面的实例是基类 Animal 声明了一个指针 animal。然后通过基类的指针来访问 Dog 类对象与 Cat 类的对象的 run()方法,前提是基类的 run()方法必须声明为虚函数,如果不声明为虚函数,基类的指针将访问到基类自己的 run()方法。我们可以尝试把 virtual 关键字去掉再重新编译测试,如果不加关键字 virtual 会是什么情况。
第 44 行和第 49 行,可以理解是 animal 指针实例化的过程。当基类的 run()方法定义成虚函数,编译器不静态链接到该函数,它将链接到派生类的 run()方法,进行实例化。
2.5 数据封装
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制,C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。
其实我们在第 2.2 小节开始就已经接触了数据封装。在 C++程序中,任何带有公有和私有成员的类都可以作为数据封装和数据抽象的实例。通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。这通常应用于数据成员,但它同样适用于所有成员,包括虚函数。
下面我们还是以狗类为例,增加一个食物的方法 addFood(int number)。将获得食物的方法设定在 public 下,这样 addFood(int number)方法就暴露出来了,也就是对外的接口。然后我们设置狗类的私有成员(private)食物的份数 total。我们在这个教程里第一次使用 private,在这章节里我们也可以学到什么时候该使用 private 什么时候使用 public。total 为获得的食物总数,然后我们还写一个公开的方法 getFood()在 public 下,通过 getFood()来打印出小狗总共获得了几份食物。
新建一个目录 10_encapsulation_example,编辑一个 10_encapsulation_example.cpp 内容如下。
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:string name;Dog(int i = 0){total = i;}void addFood(int number) {total = total + number;}int getFood() {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;
} 第 10 至第 13 行,在构造函数里初始化 total 的数量,不初始化 total 的数量默认是随 int 类型的数。所以我们需要在构造函数里初始化,也体现了构造函数的功能,一般是在构造函数里初始化。不要在类内直接赋值初始化,有可能有些编译器不支持。
第 15 至 17 行,addFood(int number),在这个方法里,将获得的食物份数赋值给 total。
第 19 至 21,getFood(),在这个方法里,将返回食物的总份数。通过调用这个方法,即可访问私有成员的 total 总数。
第 33 和 34 行,添加食物的份数。
第 36 行,打印食物的总份数。
2.6 数据抽象
数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。数据抽象是一种依赖于接口和实现分离的编程(设计)技术。数据抽象的好处:
1. 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
2. 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
举个简单的例子,比如我们生活中的手机。手机可以拍照、听音乐、收音等等。这些都是手机上的功能,用户可以直接使用。但是拍照的功能是如何实现的,是怎么通过摄像头取像然后怎么在屏幕上显示的过程,作为用户是不需要知道的。也就是暴露的不用太彻底,用户也不必须知道这种功能是如何实现的,只需要知道如何拍照即可。就 C++ 编程而言,C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。其实像 cout 这个对象就是一个公共的接口,我们不必要知道 cout 是如何在屏幕上显示内容的。cout 已经在底层实现好了。
在上一节我们已经学习过数据封装,数据封装是一种把数据和操作数据的函数捆绑在一起的机制,而数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
C++ 程序中,任何带有公有和私有成员的类都可以作为数据抽象的实例。
2.7 接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
因此,如果一个 ABC 的子类需要被实例化,则必须实现每个虚函数,这也意味着 C++ 支持使用 ABC 声明接口。如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。可用于实例化对象的类被称为具体类。
根据概念我们来写个实例来说明抽象类。
还是以狗类为说明,例程与 2.4 小节类似,只是 Aninmal 类的 run()方法定义为纯虚函数,纯虚函数不用实现,由派生类 Dog 和 Cat 类实现重写即可。
新建一个目录 11_abstract_class,编辑一个 11_abstract_class.cpp 内容如下。
#include <iostream>
using namespace std;/* 定义一个动物类 */
class Animal
{
public:virtual void run() = 0;
};
/* 定义一个狗类,并继承动物类 */
class Dog : public Animal
{
public:void run() {cout<<"Dog 的 run()方法"<<endl;}
};/* 定义一个猫类,并继承动物类 */
class Cat : public Animal
{
public:void run() {cout<<"Cat 的 run()方法"<<endl;}
};int main()
{/* 实例化 dog 对象 */Dog dog;/* 实例化 cat 对象 */Cat cat;/* dog 调用 run()方法 */dog.run();/* cat 调用 run()方法 */cat.run();return 0;
} 虽然结果和例程与 2.4 小节一样,但是却表现了两种不同的思想。学 C++重要的是思想,当我们对这种思想有一种的了解后,不管是 Qt 或者其他 C++程序,我们都能快速学习和了解。C++的内容就到此结束了。在这个 C++基础中,我们的例子非常简单,也十分之易懂,重要的是理解概念,许多 C++的课程都是以 C++的功能甚至是很复杂的算法作讲解,内容复杂且多。只要我们理解好上面的 C++的基础,对学习 C++有很大的帮助。
相关文章:
Linux-C/C++《C++/1、C++基础》(C++语言特性、面向对象等)
这里主要介绍概念为主,主要介绍 C与 C 语言中常用的不同点,和一些新的变化。其中不会去说指针、数据类型、变量类型、判断和循环等这些知识,这些和C 语言基本是一样使用的。我们主要学习 C的面向对象编程,对学习 Qt 有很大的帮助。…...
交易所开发:数字市场的核心动力
数字资产交易所作为连接用户与市场的核心枢纽,已成为推动数字经济发展的关键引擎。其开发不仅需要技术创新,还需兼顾用户体验、合规安全与生态构建,以下是交易所开发的核心要素与实践路径分析: 一、交易所的核心定位与技术架构…...
Spring Boot 应用(官网文档解读)
Spring Boot 启动方式 SpringApplication.run(MyApplication.class, args); Spring Boot 故障分析器 在Spring Boot 项目启动发生错误的时候,我们通常可以看到上面的内容,即 APPLICATION FAILED TO START,以及后面的错误描述。这个功能是通过…...
.Net面试宝典【刷题系列】
文章目录 1、JIT是如何工作的2、值类型和引用类型的区别3、解释泛型的基本原理4、如何自定义序列化和反序列化的过程5、如何使用 IFormattable 接口实现格式化输出6、请解释委托的基本原理7、什么是链式委托8、请解释反射的基本原理和其实现的基石9、如何利用反射来实现工厂模式…...
Unity游戏制作中的C#基础(3)加减乘除算术操作符,比较运算符,逻辑与,或运算符
1. 基本算术运算符 算术运算符主要用于对数值类型(整型和浮点型)进行基本的数学运算。以下是常见的算术运算符及其说明: 运算符描述示例结果加法运算符,用于两个数相加,也可用于字符串连接int a 5 3; string str &…...
如何优化 Webpack 的构建速度?
优化 Webpack 的构建速度是现代前端开发中至关重要的任务。随着项目规模的扩大,构建时间可能会显著增加,影响开发效率。以下是一些实用的方法和策略,以帮助你优化 Webpack 的构建速度。 一、使用生产模式和开发模式 1. 生产模式与开发模式 …...
win10把c盘docker虚拟硬盘映射迁移到别的磁盘
c盘空间本身就比较小、如果安装了docker服务后,安装的时候没选择其他硬盘,虚拟磁盘也在c盘会占用很大的空间,像我的就三十多个G,把它迁移到其他磁盘一下子节约几十G 1、先输入下面命令查看 docker 状态 wsl -l -v 2、如果没有停止…...
conda 配置源
无论是Anaconda vs Miniconda vs Miniforge 中的哪个,只要使用conda就涉及源,换源的目的是为了加速包的获取 修改配置文件 通过修改用户目录下的 .condarc 文件来使用 不同系统下的 .condarc 目录如下: Linux: ${HOME}/.condarcmacOS: ${…...
使用nvm管理node.js版本,方便vue2,vue3开发
在Vue项目开发过程中,我们常常会遇到同时维护Vue2和Vue3项目的情况。由于不同版本的Vue对Node.js 版本的要求有所差异,这就使得Node.js 版本管理成为了一个关键问题。NVM(Node Version Manager)作为一款强大的Node.js 版本管理工具…...
Linux离线环境安装miniconda并导入依赖包
一、实现目标 在Linux离线环境中安装miniconda后,将联网环境中的依赖包导入到离线miniconda中,使得python项目在Linux离线环境中正常运行 二、前置条件 设备需要拷贝的文件联网Linux虚拟机miniconda安装包、依赖包、项目文件离线Linux虚拟机/ 三、实…...
【opencv】图像基本操作
一.计算机眼中的图像 1.1 图像读取 cv2.IMREAD_COLOR:彩色图像 cv2.IMREAD_GRAYSCCALE:灰色图像 ①导包 import cv2 # opencv读取的格式是BGR import matplotlib.pyplot as plt import numpy as np %matplotlib inline ②读取图像 img cv2.imread(…...
泛微OA编写后端Rest接口
泛微OA编写后端Rest接口 前言 具体实现 运行结果 注意要点 总结 前言 在泛微E9中,可以通过注解的方式来编写对外的接口,之前的版本都是通过编写servlet类,然后在web.xml文件中将这个类和访问路径进行编辑之后才好在浏览器中通过输入对应…...
AI助力下的PPT革命:DeepSeek 与Kimi的高效创作实践
清华大学出品《DeepSeek:从入门到精通》分享 在忙碌的职场中,制作一份高质量的PPT往往需要投入大量时间和精力,尤其是在临近截止日期时。今天,我们将探索如何借助 AI 工具 —— DeepSeek 和 Kimi —— 让 PPT 制作变得既快捷又高…...
002 SpringCloudAlibaba整合 - Feign远程调用、Loadbalancer负载均衡
前文地址: 001 SpringCloudAlibaba整合 - Nacos注册配置中心、Sentinel流控、Zipkin链路追踪、Admin监控 文章目录 8.Feign远程调用、loadbalancer负载均衡整合1.OpenFeign整合1.引入依赖2.启动类添加EnableFeignClients注解3.yml配置4.日志配置5.远程调用测试6.服务…...
计算机视觉之图像处理-----SIFT、SURF、FAST、ORB 特征提取算法深度解析
SIFT、SURF、FAST、ORB 特征提取算法深度解析 前言 在图像处理领域亦或是计算机视觉中,首先我们需要先理解几个名词: 什么是尺度不变? 在实际场景中,同一物体可能出现在不同距离(如远处的山和近处的树)…...
DeepSeek接入Siri(已升级支持苹果手表)完整版硅基流动DeepSeek-R1部署
DeepSeek接入Siri(已升级支持苹果手表)完整版硅基流动DeepSeek-R1部署 **DeepSeek** 是一款专注于深度学习和人工智能的工具或平台,通常与人工智能、机器学习、自动化分析等领域有关。它的主要功能可能包括:深度学习模型搜索&…...
elabradio入门第八讲——帧同步技术
一、帧同步的相关知识 数字通信中, 为了使接收到的码元能够被理解,需要知道其如何分组。一般说来,接收端需要利用帧同步码去划分接收码元序列。将标志码组开始位置的帧同步码插入于一个码组的前面,如图所示。 这里的帧同步码是一…...
站长工具SEO综合查询是什么?怎么利用站长工具SEO综合查询
你的网站是否像一辆没有仪表盘的车?明明在狂奔却不知道油耗、时速、故障灯状态?2025年SimilarWeb数据显示,83%的站长因缺乏数据化诊断工具,导致50%以上的流量白白流失。今天我们用“修车师傅”的视角,拆解SEO综合查询工…...
超简单理解KMP算法(最长公共前后缀next数组、合并主子串、子串偏移法)
KMP算法理解 最长公共前后缀next合并主子串子串偏移 参考b站:子串偏移、合并主子串 最长公共前后缀next 这个概念是一个trick,帮助我们记录遍历了一遍的数组的相似特性,想出来确实很nb,我也不理解逻辑是怎么想出来的。 字符串的…...
【每日论文】TESS 2: A Large-Scale Generalist Diffusion Language Model
下载PDF或阅读论文,请点击:LlamaFactory - huggingface daily paper - 每日论文解读 | LlamaFactory | LlamaFactory 摘要 我们推出了TESS 2,这是一种通用的指令跟随扩散语言模型,其性能优于当代的指令调整扩散模型,有…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
