编程与计算机科学
编程并不能跟计算机科学划等号,其仅仅是计算机科学领域最重要的工具和研究手段之一。
编程对于计算机科学的作用,就相当于望远镜对于天文学的作用。
计算机科学是知识,而编程是技能。学习技能的正确思路,应该是以动手练习为主,看书学习为辅。
c++包含多个范式,例如面向对象编程,函数式编程,模板,以及继承自纯c语言的面向过程编程等。
“c++精通很难”这样的说法其实是一种误解,因为程序员不需要刻意去使用c++的各种特性,c++本身也并不强迫你必须掌握某些特性才能编写你的程序。
c++就像自助餐一样,各取所取即可,你不需要刻意地使用那些复杂的特性,只要能完成你的工作就行。
程序语言可以分为:
- 机器语言
- 汇编语言
- 高级语言
- 编译型语言
- 解释型语言
面向对象编程
面向对象编程是一种非常强大的,非常重要的编程范式,其可以极大地提高代码的重用性、灵活性和扩展性,是现代软件工程活动至关重要的基础之一。
现实中的一切都可以抽象成对象,无论简单和复杂。
面向对象编程有四个主要特点:抽象、封装、继承和多态。
抽象
面向对象中方法中的抽象,是指对一系列具体问题进行概括,抽出一类对象的公共性质并加以描述的过程。
对一个问题的抽象分两个方面:
- 数据抽象
- 描述某类对象的特性和状态
- 行为抽象
- 描述某类对象的功能或者行为特性
封装
封装就是将抽象得到的数据和行为(或者功能)相结合,形成一个有机的整体,也就是c++中的类(class),其中的数据和函数都是类的成员。
继承和多态
- 继承的含义是,新建了一个继承了原有类的新类,具有原有类的所有特征的同时,又具有自身的新特性。
- 多态则是面向对象程序设计,对于人类思维方式的一种直接模拟。从广义上说,多态性指的是同一段程序,可以直接处理多种类型对象的能力。
类和对象
- 类是面向对象程序设计方法的核心——通过类,我们可以实现对于数据的封装和隐藏。
- 当我们定义了一个类之后,就可以定义这个类的变量,这个变量就是类的对象,或者叫做实例,而定义的过程,则成为实例化。
- 在类中定义的函数和数据,分别称为数据成员和函数成员。
class 类名称 {
public:
外部成员
protected:
保护型成员
private:
私有成员
};
类和对象的使用
可在声明一个类Class ClName后,在其他地方通过ClName cname实例化类,获得类对象。
类的构造函数
构造函数是一种定义在类体中的特殊函数,它可以被用于对类进行初始化的算法。
在语法上,构造函数具有这样的性质:
- 函数名与类名完全相同;
- 不能定义返回值类型,也不能有return语句;
- 可以有形参,也可以没有形参,可以带有默认参数(称为默认构造函数)
- 可以重载
一般构造函数不需手动调用,在创建对象的时候,构造函数会被自动调用。
Clock MyClock(30, 10, 3);//自动调用类中的构造函数
默认构造函数
默认构造函数:参数为默认值,或无参数,例如:
Clock();
Clock(int newH = 0, int newM = 0, int newS = 0);
注意:同一个类中不能出现两个默认构造函数
如果我们在定义一个类的时候,不声明任何构造函数,那么编译器在编译的时候,就会为我们自动生成一个默认构造函数,该构造函数特点是“什么也不做”,例于Clock(){}
复制构造函数(又称拷贝构造函数)
复制构造函数是用一个已有的对象,来执行一个新对象的构造,其具有一般构造函数的所有特性——它的形参是本类的一个对象的引用,作用是用一个已经存在的对象来初始化一个新对象。
复制构造函数使用的是引用传递1。
若我们未定义类的构造函数,则系统会在必要的时候,隐含生成默认复制构造函数,这个隐含的复制构造函数的作用是,把初始值对象的每一个数据成员的值都复制到新建的对象中,因此可以说是完成了对同类对象的复制。
复制构造函数具体声明方式如下:
class 类名 {
public:
类名(const 类名& 对象名) {
//实现
}
};
具体代码示例:
class Point {
public:
Point(const Point &p);
private:
int x, y;
};
Point::Point(const Point &p) {
x = p.x;
y = p.y;
}
注意:以上代码中,复制构造函数通过一种看似“不合法”的方式,访问了Point类的实例对象p的两个私有成员变量。但private和public的区别是对类来说的,而不是对对象来说的。复制构造函数是Point类的成员函数,所以其能访问类的私有成员变量,这跟具体的对象无关。
普通构造函数(包括默认构造函数)是在对象创建的时候被调用,复制构造函数会在什么时候被调用?
- 当用类的一个对象去初始化该类的另一个对象的时候:
Point a(1, 2); Point b(a);//用对象a初始化b,复制构造函数被调用 Point c = a;//用对象a给对象c赋值,复制构造函数被调用
- 当函数的形参是类的对象,调用函数时进行形实结合的时候:
void f(Point p) { //code here } int main() { Point a(1, 2); f(a); return 0; }
- 当函数的返回值是类的对象,函数执行完成返回调用者的时候
Point g() { Point a(1, 2); return a; //返回的并非a本身,而是在主调函数中用a重新构造的新对象 }
复制构造函数所带来的
- 浅复制——未能将对象的资源同时复制过来,即为浅复制,默认复制构造函数只能浅复制
- 深复制——将对象的资源同时复制过来,即为深复制,需要自定义复制构造函数。
初始化列表和析构函数
初始化列表
初始化列表即为在类的构造函数的参数列表后面,写上要用哪个参数,来初始化哪个变量,写法如下:
构造函数(参数列表):成员变量(常量或者参数) {
//构造函数的函数体
}
class foo
{
public:
foo(string s, int i):name(s), id(i){ // 初始化列表
//构造函数的函数体
}
private:
string name ;
int id ;
};
初始化列表能够提高编码效率,能直接将要赋的值来初始化成员变量,若不用初始化列表,则要“先建立一个对象,初始化成员变量,然后再为成员变量一一赋值”,较为繁琐。
简言之,初始化列表在效率上相对于构造函数中的赋值,会存在一定优势,比如在调用开销的节约方面。
在实际的程序执行中,一个类被实例化成一个对象,整个过程实际上可分为两个阶段:
- 对象的构造和成员变量的初始化——初始化列表就是在此期间发挥作用的;
- 执行构造函数中的操作。
析构函数
想回收泄露的内存,只有两种办法:
- 给内存重新通电(即重启机器),清除内存中的所有数据
- 进程结束时,由操作系统负责回收它所使用的所有内存空间
析构函数的作用就是在对象结束的时候,自动调用该函数释放资源,避免出现内存泄漏。
同样,如果没有自定义的析构函数,则编译器会自动生成一个默认的析构函数。
简言之,如果希望程序在对象被删除之前,自动地来完成某些事情,就可以把代码写到析构函数里,析构函数示例如下:
class Clock {
public:
~Clock() {
//写入想在对象结束时自动执行的命令
}
};
##命令空间
对于c++程序来说,标识符的作用域有:
- 函数原型作用域
c++中最小的作用域,在函数原型声明时形式参数的作用范围就是函数原型作用域
- 局部作用域(块作用域)
指的是同一函数体内,使用大括号分割的不同代码,较为常见的是选择结构和循环结构的局部作用域。
- 类作用域
类可以看作是一组有名字的成员的集合,具体说某一个类class成员member具有类作用域
- 命名空间(namespace)
一个命名空间自身即确定了一个作用域,凡是在该命名空间内声明的,不属于前面提到的各个作用域的标识符,都属于这个命名空间的作用域。
namespace的格式
namespace 命名空间名{
命名空间内的各种声明(函数声明, 类声明等)
}
在命名空间内部可以直接引用当前命名空间的标识符,如果要引用其他命名空间的表示符,则用其他命名空间::标识符
来引用。
namespace NS {
class Class{
//类成员
};
}
NS::Class object;
使用c++中的using
using 命名空间::标识符名 //将其直接暴露在当前作用域,即可直接使用该标识符
using namespace 命名空间 //直接引用整个命名空间,即可直接使用空间内部的所有标识符
类似于函数,命名空间也是允许嵌套,比如:
namespace Outer {
namespace Inner {
class Class {
//类成员
};
}
}
如果要引用内部的Class类,则这样应用:
using Outer::Inner::Class;
注:一般工程上不会直接使用using namespace std
这样的写法,因为这种写法会造成命名空间污染,在实际开发中很容易导致不明确的行为(例如不可移植)。
类的组合
- 类的组合描述的就是一个类内嵌其他对象作为成员的情况,他们之间的关系是一种包含和被包含的关系。
- 当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将会首先被自动创建。故创建对象时,既要对类中基本的数据类型进行初始化,也要对内嵌对象进行初始化。
组合类构造函数一般的定义形式如下:
类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表)...{
//初始化函数体
}
当创建某类的对象时,该类调用构造函数的顺序为:
- 调用内嵌对象的构造函数,若有多个内嵌对象,则按照其在组合类中定义的顺序调用。(注意:各内嵌对象在参数列表中的顺序与实际的构造函数调用顺序无关)
- 本类的构造函数的函数体
对应的析构函数调用顺序与构造函数调用顺序是相反的。
前向引用声明和结构体
前向引用声明
类应该先声明后引用,但如果需要在某个类的声明之前引用该类,则应该进行前向引用声明。前向引用声明只为程序引入一个标识符,但具体的声明在其他地方。
示例如下:
class B;
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
注意:当使用前向声明时,只能使用被声明的符号,而不能涉及类的任何细节。例如:
class B;
class A {
public:
B b; //错误,此时类B的定义尚不完善,但可以改为 B& b;
};
class B {
public:
void g(A a);
};
结构体
- 结构体是一种特殊形态的类,其和类的使用方法几乎一样。结构体内成员默认为公有成员。
- 类和结构体并存,是由于历史原因造成的,但要是仅仅想把一系列不同类型的数据组合成一个整体,以便于保存,则可考虑用结构体,用类保存反而较为麻烦。
为什么这里要使用引用来传参,而不是采用值传递?值传递就是当函数调用时,给形参分配内存空间,然后用实参的值来初始化形参。如果参数是对象的话,那么对于值传递来说,“初始化形参”这个过程会造成额外的时间开销,浪费系统资源。而使用引用,则不会有这个问题。 ↩︎