C++创建对象:
-
User u(... );
- 把u本身当作一个对象
-
User *p = new User(...);
- 构造函数调用会创建一小块内存区域,用于存储p的值(一个内存地址),然后填充一块较大的区域,赋值符右侧所创建的User对象就存储在这块内存中。p所指向的内存地址就是User对象的存储位置
u:
--- ---
| |
| |
| |
| |
| |
--- ---
p:
---------
| |
---------
|
|
- - - → User对象
- - - -
| |
| |
| |
- - - -
对于Java代码 User q = new User(...)
。变量q保存了指向那个对象的指针(以对象引用的形式)。 与Java不同的是,JVM由于内存管理策略的原因决定把User对象移动到内存中的一个不同位置,q所保存的引用仍然能够找到这个对象。而C++中的p所指向的对象移动到不同的位置,p的值需要进行显式的修改。
C++定义子类
一个简单的例子:
// User.h
#include <string>
#include <iostream>
class User {
std::string name;
int age;
public:
User(std::string name, int age) {
this->name = name;
this->age = age;
}
void print() {
std::cout << "Name: " << name << ", Age: " << age;
}
};
// StudentUser.h
#include <string>
#include "User.h"
class StudentUser : public User {
std::string schoolEnrolled;
public:
StudentUser(std::string name, int y, std::string school) : User(name, y) {
schoolEnrolled = school;
}
void print() {
User::print();
std::cout << ", School Enrolled: " << schoolEnrolled;
}
};
-
StudentUser继承User类
- 子类将继承基类的所有成员,子类可能可以访问某些成员,但也可能访问不到。
- 由于name和age都处于User的私有部分,StudentUser不能直接访问访问它们。
-
StudentUser的构造函数后对基类构造函数进行了调用
一个派生类的构造函数在执行任何任务之前必须调用它的基类的构造函数。
如果派生类的构造函数中并没有显式地调用基类的构造函数,系统将会试图调用基类的无参构造函数(缺省构造函数)
-
User::print();
调用了父类User所定义的print函数来打印那些无法在子类中直接访问的成员
下面是一个简单的C++多态演示;想要一个C++程序利用多态特性,需要满足两个条件
对象必须通过指针或者引用进行操纵
期望产生多态行为的函数必须在基类中声明为虚函数,一个虚函数被继承后仍然能够保持虚状态
将上面User类的print函数添加virtual修饰
virtual void print() { ... }
测试代码:
#include "User.h"
#include "StudentUser.h"
int main() {
User* users[3];
users[0] = new User("0", 0);
users[1] = new StudentUser("1", 1, "1");
users[2] = new User("2", 2);
for (int i = 0; i < 3; ++i) {
users[i]->print();
std::cout << std::endl;
}
delete users[0];
delete (StudentUser*)users[1]; // 强转
delete users[2];
return 0;
}
打印结果:
Name: 0, Age: 0
Name: 1, Age: 1, School Enrolled: 1
Name: 2, Age: 2
内存管理另一个方式就是在基类User中定义一个函数体为空的虚析构函数
,然后delete[]即可
delete[] users;
而对于Java来说,类似的实现多态并不需要满足特别的条件
- 不需要使用任何特殊的方式来操纵Java对象
- 不需要像函数添加任何特殊的指示符 (如
virtual
) 使他们具有多态行为
阻断继承
Java中final修饰的类禁止被继承,final修饰的方法禁止被子类重写。C++中没有特殊的关键字做这类事情。
我们可以把C++类的构造函数放在私有部分,这样它就不能够被继承
对象的打印
Java中只要重写从java.lang.Object类中继承过来的toString方法,就能够自定义打印的形式。
在Java中,如果User类重写toString方法,那么sout输出的就是user.toString()返回的字符串。
User user = new User();
System.out.println(user);
C++有没有类似的功能?答案是一般选择重载 <<
操作符
friend std::ostream& operator << (std::ostream& os, const User& user) {
os << "Name: " << user.name << ", Age: " << user.age;
return os;
}
运行下面代码, 输出为:Name: us, Age: 1
User us("us", 1);
std::cout << us << std::endl;
对象的销毁
-
对象在离开作用域的时候,它的析构函数会被自动调用
-
析构函数不应该有返回值,任何函数入参,析构名为类名前加个~
-
如果一个类没有显式提供析构函数,那么系统在销毁这个对象时会调用析构函数的缺省行为:
- 依次调用类的每个数据成员的析构函数
- 对于基本类型的数据成员,销毁就是简单地释放它们所占据的内存
C++的命名空间
与Java类似的词为包。
namespace ModuleName {
// code
}
使用namespaceName::foo()
调用命名空间中的函数foo()
-
全局命名空间
::foo()
-
使用
using namespace Name;
调用特定函数时可以省略命名空间的限定
命名空间的别名:
namespace X = N1::N2::N3::N4;
类成员的访问控制
C++中,成员的访问控制属性可以是private,protected,public的。访问控制的目的是规范对象状态的修改方式。
C++不加访问修饰,那么默认为private的。
public:
- 类的public成员可以被其它所有类和函数进行访问
private:
-
私有成员可以被类的友元(friend)访问(友元声明可以放在类的private, public, protected, 缺省 任何地方)
-
虽然私有成员能够被子类全部继承,但是并不能够被子类直接访问。
protected:
- 类成员只能由这个类本身,及其子类访问。
class X {
// ...
friend Y; // 声明 friend类Y,Y中能够直接访问X的成员
friend void print(X*); // 声明一个全局friend函数print
}
抽象类和接口
如果我们无法从一个类创建对象,那么这个类就是抽象类。
C++中,如果 一个类的一个或者多个函数被声明为纯虚函数, 那么我们就无法从这个类直接创建对象
抽象类的作用:
- 类层次体系中起到组织其它类的作用
- 抽象类可以表示一种特殊的行为,当它与其它类混合使用时,可以允许我们创建具有这种行为的类
- 抽象类可以帮助我们以增量的方式创建类的实现代码
抽象类把那些分散在各个字类中的共同概念归纳在一起。
比如
- Shape
- Circle
- Rectangle
- …
class Shape {
public:
// 面积
virtual double area() = 0;
// 周长
virtual double circumference() = 0;
// ...
}
=0
表示该函数为纯虚函数,编译器要求该函数不包含任何实现代码。
对象的比较
在Java中,引用相等性比较通过“==”;内容相等性比较通过 “equals”方法。
-
C++的string已经重载了 ”==“操作符,认为字符序列相同,那么就相等。
-
C++容器的各种排序算法需要进行比大小,在比较时可通过函数对象的形式提供,也可以通过重载 “<” 操作符
- 另外string提供了compare函数,能够返回-1, 0, 1
内部类
todo …