BUAA c++ 复习笔记


老师给的复习思维导图

函数重载:同名,不同参

  • 省略形参名字也是同一函数
  • 返回类型不同,其他都相同,报错
  • 重载:优先选用非常量版本的函数
  • 形参可以是某种类型的指针或引用
#include <iostream>
using namespace std;

void Print(int i) {
	cout << 1 << i << endl;
}

void Print(char* str) {
	cout << 2 << str << endl;
}

int main(int argc, const char* argv[]) {
	char* str = " hello world";
	Print(str);
    
	return 0;
}

默认传参必须放在后面

void fun(int a , int b = 20, int c = 10)
占位符:

函数声明可以没有具体名字,满足重载要求,因为可以区分不同函数

void Print(char *){};

#include <iostream>
#define PI 3.14
#define ADD(a, b) a + b 
// 小函数频繁调用,普通函数调用要保存现场、恢复现场(堆栈),减少程序运行时间,易错少用。
using namespace std;

int main(int argc, const char* argv[]) {
	int x = 1;
	int y = 2;
	printf("%f\n", PI * x);
	printf("%d\n",ADD(x, y));
	return 0;
} 

控制宏/预编译控制宏:#include

#include <iostream>
#define len(a) sizeof(a) / sizeof(a[0])
using namespace std;

int main() {
	int  a[5] = { 1, 3, 6, 9, 2 };
	cout << len(a) << endl;
}

条件编译

有选择地对部分程序源代码进行编译。
e.g.只在调试时进行编译,调试开关可以使用一个宏来实现

#ifdef DEBUG
   cerr <<"Variable x = " << x << endl;
#endif

C++有预定义表明身份的宏

#ifdef __cplusplus  // 预定义宏,表示身份
extern "C"
{
    void c_fun();
}
#endif
动态、静态库
  • dynamic: .dll
  • static: .lib
  • 区别:是否参与源程序的生成

导入库
#pragma comment(lib,"testlib.lib")

按C的方式解释函数
#include <iostream>
using namespace std;

extern "C" void c_fun();

void c_fun() {
	printf("hello world!\n");
}

int main() {
	c_fun();
	return 0;
}

or

#ifdef __cplusplus  // 预定义宏,表示身份
extern "C"
{
    void c_fun();
}
#endif

#include <iostream>

void c_fun() {
    printf("hello world!\n");
}

int main() {
    c_fun();
    return 0;
}

封装:

friend 友元机制
普通非成员函数友元
  • 友元的声明默认为了extern,作用域已经扩展到了包含该类定义的作用域
  • 友元函数的特点是能够访问类中的私有成员的非成员函数
  • 它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
  • 调用同普通函数
  • 友元不具有相互性,只具有单向性。(若类B是类A的友元,类A不一定是类B的友元)
  • 友元不能被继承
  • 友元不具有传递性(B是A的友元,C是B的友元,推不出C是A的友元)

e.g.

#include <iostream>
using namespace std;

class Point {
 private:
	double x, y;

 public:
	friend double Distance(Point& a, Point& b);  // friend表示他不是成员函数,是友元函数
	Point(double x, double y) {
		this->x = x; 	// attention!这里是->
		this->y = y;	// 如果用的是.会报错:“member reference type 'Point*' is a pointer...”
	}
};
double Distance(Point& a, Point& b) {
	double length;
	length = a.x - b.x + a.y - b.y;
	return length;
}

int main() {
	Point p(2, 3);
	Point q(4, 5);
	cout << Distance(p, q) << endl;
	return 0;
    
}

友元类
类成员函数作为友元函数
struct结构体
#include<iostream>
using namespace std;

struct Student{
	// member
	// attribute 属性
	int age;
	char* name;
	//...
	// method 方法
	void init(int a_age,char* a_name);
};

// :: 表示Student的init,虽然定义在外面,但是还是Student结构体内的函数
void Student::init(int a_age,char* a_name){
	age = a_age;
	name = a_name;
}

int main(){
	Student s;

	s.init(20,"zhang");

	cout << s.name << ", " << s.age << endl;
	cout << sizeof(s) << endl;   // 输出16

	return 0;
}

操作与类型无关

  • C语言,操作与类型无关。可以自定义结构,结构是属性的集合,不包含任何函数。
  • 函数不占运行期内存
  • 默认对齐规则:按大对齐,以下代码12字节
int i; //4字节 
int j; //4字节
char c; // 1 Byte
char ch; // 1 B

以下代码16字节

double d;  	// 8B
int i;  	// 4B
char c;  	// 1B
char ch; 	// 1B

以下代码24字节

int i;		// 4B --> 8B
double d;	// 8B
char c;		// c&ch占用8字节
char ch;	// c&ch占用8字节

以下代码16B question!!!

	int   age;  // 4B->8B
	char* name;  // 8B
	void init(int a_age, char* a_name); // 加或者不加这句都是16B,函数不占运行性空间

  • 类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。
  • 也可以在类的外部使用范围解析运算符 :: 定义该函数
double Box::getVolume(void)
{
    return length * breadth * height;
}
访问控制
  • public: 公有
  • private: 私有。
    • 私有不能s.age = 21;
  • protected: 与继承有关

struct 内部默认公有,不放函数。class 内部默认私有,可以放函数。
private 可省略,在类内部,默认私有

class Student{
	// member
	// attribute 属性
private:
	int age;
	char* name;
	//...
	// method 方法
public:
	void init(int a_age,char* a_name);
	int get_age();
};
int Student::get_age(){
	return age;
}

等价于

int Student::get_age(){
	return this->age;
}

指针可以改变private成员

#include <iostream>
using namespace std;
class Student {
	// member
	// attribute 属性
 private:
	int   age;
	int   age2;
	char* name;
	//...
	// method 方法
 public:
	void init(int a_age, int a_age2, char* a_name);
	int   get_age();
	int   get_age2();
	char* get_name();
};

void Student::init(int a_age, int a_age2, char* a_name) {
	age = a_age;
	age2 = a_age2;
	name = a_name;
}

int Student::get_age() {
	return age;
}

int Student::get_age2() {
	return age2;
}

char* Student::get_name() {
	return name;
}

int main() {
	Student s;

	s.init(20, 10, "zhang");
	cout << "age = " << s.get_age() << endl;
	cout << "age2 = " << s.get_age2() << endl;
	cout << "name = " << s.get_name() << endl;
	int* p = (int*)&s;
	*p = 30;
	p += 1;
	*p = 40;
	char** p1 = (char**)(&s);
	// 此时,**p1 = 'z';*p1指向字符串“zhang”
	p1 += 1; // 因为一个char**是8B,所以,要指向char* name只需要增加一个8B即可。
	*p1 = "gong";
	cout << "age = " << s.get_age() << endl;
	cout << "age2 = " << s.get_age2() << endl;
	cout << "name = " << s.get_name() << endl;
	return 0;
}

output:
age = 20
age2 = 10
name = zhang
age = 30
age2 = 40
name = gong

int main(int argc, char* argv[]) {
	Student  s(10, "zhang");  // 创建对象时会自动调用构造函数
	Student* s1 = new Student(12);

	cout << s.get_name() << s.get_age() << endl; // 对象用.
	cout << s1->get_name() << s1->get_age() << endl; // 指针用 ->

	return 0;
}
同名构造器
class Student {
 private:
	int   age;  
	char* name;
 public:
 	Student(); // 默认构造器,即使有其他构造函数,也最好定义一个默认构造函数
 				// 构造函数最少一个,即默认构造器
	Student(int aage);
	Student(int aage, char* aname); 
};

Student::Student(int aage, char* aname) { 
	age = aage;
	name = aname;
}

Student::Student(int aage) {
	age = aage;
	name = "liu";
}
使用默认构造函数

使用自定义构造函数传参时,必须用括号将所有的参数传入构造器,但是在使用默认构造器时,不需要传参,也不需要括号
即:

	Father father3;

就已经定义了一个对象了。而使用自定义构造器,必须传参,就是:

Father father1(1, 1)
ClassName obj(); // 定义了一个函数,是一个不接受参数的,返回值是ClassName类型对象的函数。
ClassName obj;   // 定义了一个对象。

委托构造函数
#include <iostream>
using namespace std;

class Student {
	char* name;
	int   id;

 public:
	Student(char* aname, int aid)   // this is 构造函数
		: name(aname), id(aid) {}
	Student()						// they are 委托构造函数
		: Student("", 0) {}
	Student(char* s)
		: Student(s, 0) {}
	Student(int i)
		: Student("", i) {}

	char* getname() {
		return name;
	}
	int getid() {
		return id;
	}
};

int main() {
    
	Student t("zhang");
	Student s(17);

	cout << t.getname() << s.getid() << endl;

	return 0;
}
析构函数:对象消亡的时候自动调用。
  • 消亡无条件,不传递参数,所以不存在重载。一个类只有唯一一个析构函数。
  • 作用:释放本类空间。
  • 若有new(动态分配),则一定有析构函数。构造器中没有new,不代表其他函数中没有new,所以还可能有析构函数。
  • 析构函数体自身并不直接销毁成员,成员是在析构函数体之后隐含的析构阶段中被销毁的。
~Student();
  • 不能delete⼀个野指针(消除野指针this->j = NULL;
Test::Test(int a)
{
	this->i = a; // 只初始化了i。没有管j,fly pointer
	// 那么后续delete,释放一个野指针时,会崩溃(因为~Test()中是delete j 
	// 所以此方法需要添加下一行
	this->j = NULL; 
} 
Student::~Student(){
    delete name; // delete 最好放在析构函数中
}
Test *p = new Test(1, 2); // p是⼀个放在栈区的局部变量,new出来的在堆空间
delete p;// 释放p指向的空间,不删除p指针本身。

main函数运行结束时,p指针消亡,但是p指向的空间还在,因此内存泄露,应当在main里用析构函数释放空间。

void Test::fun(){
	int *p = new int;
	//...
	delete p;
} 

现在不需析构,如何解释?
此函数无大意义,new了没用,生命周期太短。局部变量即可。

程序运⾏分区

  • 局部变量内存在栈区,动态分配的内存在堆区,堆区的内存必须使⽤后收回清理
  • 代码区:常量
  • 运⾏区(运⾏时Runtime): 堆(动态内存分配) , 栈(局部变量) , 全局变量区
  • 局部变量s1在栈区,new出来的东西在堆区。Student *s1 = new Student(12);
  • 清理clean()函数:或者用析构函数~Student()
  • 栈:
    由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
    堆:
    需要程序员自己申请,并指明大小,在c中malloc函数
    如p1 = (char *)malloc(10);
    在C++中用new运算符
    如p2 = (char *)new(10);
    但是注意p1、p2本身是在栈中的。
void Student::clean(){
	delete j;
}

动态内存分配

	Memory *p = (Memory*)malloc(sizeof(Memory));

new = malloc + constructor
建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
delete = destructor + free 先析构再free

C++ 程序中的内存分为两个部分:

  • 栈:在 函数内部声明 的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
    • new: 在运行时分配堆内的内存,这会返回所分配的空间地址
	double* pvalue  = NULL; // 初始化为 null 的指针
	if( !(pvalue  = new double )) {
   		cout << "Error: out of memory." <<endl;
   		exit(1);
	}
- delete: 删除之前由 new 运算符分配的内存。

一维数组

// 动态分配,数组长度为 m
int *array=new int [m]//释放内存
delete [] array;

二维数组

int **array; // double ptr
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m]; // ptr
for( int i=0; i<m; i++ )
{
    array[i] = new int [n]  ;
}
//释放
for( int i=0; i<m; i++ )
{
    delete [] arrar[i];
}
delete [] array;

引用:是安全的指针

  • 必须在创建时初始化
  • 永久绑定
int &r = i; // & 为引用符,引用必须初始化
r = j;     // 报错,r永久与i绑定

引用传地址,效果与指针相同

用引用类型实现函数的input, output
#include <iostream>
using namespace std;

void fun(int& i) {
	i++;
}

int main() {
	int m = 10;
	fun(m);
	cout << m << endl;
}

output:11

返回值是引用:
double& setValues( int i )
{
  return vals[i];   // 返回第 i 个元素的引用
}
// 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
setValues(1) = 20.23; // 改变第 2 个元素
#include <iostream>
using namespace std;

int arr[10];

int& setArr(int i) {
	return arr[i];
}

int main() {
	for (int i = 0; i < 10; i++) {
		arr[i] = 10 - i;
	}
    cout << arr[2] << endl;

	setArr(2) = 20;

	cout << arr[2] << endl;
}

output:
8
20

当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。

int& func() {
   int q;
   //! return q; // 在编译时发生错误: 对局部变量的引用是不合法的,主要原因是局部变量会在函数返回后被销毁
   static int x;
   return x;     // 安全,x 在函数作用域外依然是有效的
}
  • 不能返回函数内部new分配的内存的引用。

  • 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁

  • 可以返回类成员的引用,但最好是const

  • 返回*this的成员函数(返回&Screen),返回的是对象本身,而非对象的副本。
    如果返回的是Screen而非&Screen,那返回的是对象的副本。

class Screen{
	contents(ht *wd,c){}
public:
	Screen &set(char); // set成员的返回值是调用set对象的引用
}
inline Screen &Screen::set(char c){
	contents[cursor] = c;
	return *this; // this 作为左值返回。返回的是对象本身,而非对象的副本。
}

深拷贝与浅拷贝

深拷贝:

拷贝构造:拷贝构造函数的参数必须是引用类型。

  • 拷贝构造函数是一种特殊的构造函数
  • 它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。
	CExample(const CExample& C)
    {
        a=C.a;
    }
  • 如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。
  • 自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
    拷贝构造函数的一般形式
Rect::Rect(const Rect& r)
{
    width=r.width;
    height=r.height;
}

new一个指针

Test::Test(Test& t) { 	// 引用类型
	this->i = t.i;
	this->j = new int(*t.j);
}

	Test t1(1, 2);
	Test t2(t1); // 拷贝构造,传值就是传拷贝
浅拷贝:
  • memcpy函数:典型浅拷⻉,没有类型检测
  • 在对象复制时,只对对象中的数据成员进行简单的赋值.
  • 如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
  • 当数据成员中没有指针时,浅拷贝是可行的。
深拷贝和浅拷贝的区别
  • 深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。
  • 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
为什么拷贝构造函数必须是引用传递,不能是值传递

防止递归引用。

static

全局变量的值在程序的整个生命周期内都是有效的。全局变量一旦声明,在整个程序中都是可用的。

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。

全局变量和静态变量的存储是放在一块的。也就是说,在main以外的全局变量,在头文件里的全局变量,有static修饰的局部变量,都在静态区
局部变量,在栈区

static

  • 变量
    第一次被用到创建,main函数退出消亡。
    初始化要类似全局变量,在外部显式初始化。int Test::i = 10; 不能在构造器里初始化。
  • 函数
    仅本文件使用
    static函数没有this指针。
    与具体对象相关操作无关的才能修饰为static。或者操作static的函数可以static
static void print(int j) { cout << j << endl; }
int main(int argc, char* argv[]){
	int i;
	int j;
	static int m; //静态局部变量和全局变量的地址放在一起。一次创建,不再释放
	cout << &i << endl;
	cout << &j << endl;
	cout << &m << endl;
	//i j地址差4,m不与它们一起
	return 0; 
}
  • 静态成员函数不和对象绑定在一起,不包含this指针,不能在static函数体内使用this指针。
class Test {
public:
	static int i; 			// 静态成员不能被声明成const的
	int j;
public:
	Test(int aj);
	~Test(); 
	void a_fun();
	static void b_fun();	//本函数的调用将不再局限于具体对象
	void fun1() {
		i++;
	}
	void fun2() {
		cout << i <<endl;
	}
	void fun3() {
		j++;
	}
	//第三个函数前不能放static,因为j属于具体对象。
	//前两个函数可放,因为前两个都在操纵i,而i是所有类的 
};

Test::b_fun();  //不需要对象,直接通过类名调用函数 static 修饰
//static 无对象,无this下的指针(this->i) 

namespace

定义:

namespace namespace_name {
   // 代码声明
}

命名空间,防止用户自定义重名。
如果不写using namespace T1; 或者 using namespace T2; 必须显式指明使用名字空间。
using namespace可以放在代码块中间。
如果只打算使用 std 命名空间中的 cout 部分,您可以使用如下的语句: using std::cout;

int main(){
	T1:ABC_class a;
	T2:ABC_class b;
	return 0;
}

修饰符

const
  • 只读常量。
  • 放在class里。
  • 取代了C中的宏定义,声明时必须进行初始化
  • 如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。
  • 对于常量指针,不能通过该指针来改变所指的内容。以下这样是错误的。
const int *pi = &i;
*pi = 100;
int  get_i() const{ // const 表示允许常量对象调用。
        return i;
}	

常量对象这样定义:

	Test const tc(0);

或者这样:

const Test t;
  • 常量对象不允许调用方法,有几率会改变常量对象的相关值。除非方法也是const。
extern

extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。

extern

extern int global; // 外链接。表示定义不在这里。可以放头文件。
int main(){}
  • 全局变量放源文件。外链接放头文件。
    主函数所在的文件:
#include <iostream>
#include "headfile.h"
using namespace std;

int main() {
    extern int global;
	cout << global << endl;
	return 0;
}

头文件:

extern int global;

在另一个cpp文件中有定义即可(全局的)

int global = 123;
  • 函数默认外链接。除非加static

运算符重载

值得注意的是:

  1. 运算重载符不可以改变语法结构。
  2. 运算重载符不可以改变操作数的个数。
  3. 运算重载符不可以改变优先级。
  4. 运算重载符不可以改变结合性。
#include <iostream>
using namespace std;

class Account {
	char* name;
	int   id;
	int   balance;

 public:
	Account();
	void Save(int money);
	Account operator*(int money);  // 运算符重载
	int get_balance() {
		return balance;
	}
};

Account::Account() {
	balance = 0;
}

void Account::Save(int money) {
	this->balance += money;
}

Account Account::operator*(int money) {
	this->balance += money;    // 这里把运算符 * 重载成了 + 
	return *this;  // return obj
}

int main() {
	Account a;
	a.Save(100);
	a = a * 100;

	cout << a.get_balance() << endl;
}

继承

注意语法。class Rectangle: public Shape
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
#include <iostream>
using namespace std;

class Borrower {
	int   id;
	char* name;

 public:
	void borrow();
	// void returnBook();
};

void
Borrower::borrow() {
	cout << "borrow" << endl;
}

class Teacher : public Borrower {  	// 继承Borrower类。
 public:
	void borrow();
};

void // 重写borrow方法。
Teacher::borrow() { 				
	Borrower::borrow();				// 调用父类的方法。才会输出 borrow 否则没有 borrow
	cout << "5 books" << endl;
}

int main(){
    Teacher t;
    t.borrow();
    return 0;
}

重写函数

	void display() {
		Father::display();  // 类似于java中的super.display();
		cout << "after father" << endl;
	}
多继承
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,{
<派生类类体>
};

e.g.

class Rectangle: public Shape, public PaintCost
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
};

创建子类对象要 先调用父类的构造函数,析构先调用子类的析构函数。
Pet: agename调用父类的构造函数

cat(int age, char* name, int atype): Pet(aage, aname),type(atype) {
}
// type(atype) 更加面向对象的写法

new一个汽车类型,调用汽车的构造,会默认调用引擎的构造
创建一个外壳,会先调用里面内容的构造。

Car():e(1)				// 这是父类的构造方法。当engine没有默认构造时,必须显式构造

赋值顺序:先i再j,按类内声明顺序。

class Test{
    int i;
    int j;
public:
    Test(int a):j(a),i(j){}
};

多重继承,Base1Base2有函数名冲突

class Derived: public Base1 {
	Base2 b2;
public:
	void h(){
		b2.h();  // 组合:代码重用
	}
};
多态

没有继承,没有多态。
前绑定,early binding
运行期绑定,later binding, runtime binding,
动态绑定,dynamic binding

虚函数

  • 虚函数是指针vptr,指向虚函数表vtable。存储类的所有的虚函数
  • 虚函数表一个类一个。
  • 每个对象一个虚函数指针,指向本类虚函数表。
  • 调用虚函数,寻址虚函数表,找到类对应的函数。
纯虚函数
  • 写法:virtual void Speak() = 0;
  • 有纯虚函数的类是抽象类,抽象类不能被实例化。
  • 抽象类可以有数据成员。
  • 纯虚函数可以有函数体。
  • 作用:
    1. 是父类,可以当参数传递。不能传值,因为不能实例化,没有对象。
    2. 纲领,子类中必须实现在父类中已有的方法。如果不重写父类中的纯虚函数,这个子类自动继承父类的纯虚函数,那么它还是抽象类,还是不能实例化。不能创建对象。
class Pet{
	int age;
	char *name;
public:
	virtual void Speak() {				// 只要加了虚函数就多4B,不管多了几个虚函数
		cout << "speak" << endl;		
	}
	virtual void Speak() = 0;			// 纯虚函数,有纯虚函数的类是抽象类
	/* virtual void Sleep(){} */
};

class Cat : public Pet {
public:
	virtual void Speak(){				// 虚函数自动继承,可以不写,但习惯性写。纯虚函数必须写
		cout << "miao" << endl;
	}
};

class Dog : public Pet {
public:
	void Speak(){
		cout << "wang" << endl;
	}
};

void Needle(Pet &pet){
	pet.Speak();
}

int main(){
	Cat cat;
	Needle(cat);  // 子类当父类对待,向上类型转换
}
接口
  • 串联因为行为共性本不相关的家族
  • FlyObject : 接口
class FlyObject { 
 public:
	virtual void fly() = 0;
};

class Airplane : public Machine, public FlyObject {
};

class Bird : public Animal, public FlyObject {
};
构造函数不可能是虚函数,析构函数往往是虚函数
  • 构造函数前面永远不加 virtual
	Fulei *p1 = new Zilei; //  new谁调谁,所以调用子类构造。
  • 析构函数往往是多态的。
	virtual ~MyClass();
  • 在一个类里虚函数创建后多一个指针,所以有虚函数就尽量多写。

先析构继承类,再析构基类。

内联函数

在函数调用处直接嵌入函数体的函数称为内联函数。为了消除函数调用的时空开销。
一般只将那些短小的、频繁调用的函数声明为内联函数。
指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字。请看下面的例子:
e.g.1

inline void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

e.g.2

#include <iostream>
using namespace std;

inline const string& shorterString(const string& s1, const string& s2) {
	return s1.size() <= s2.size() ? s1 : s2;
}

int main() {
	cout << shorterString("hello", "hell") << endl;
	return 0;
}

output:hell

constexper函数
内联函数和constexper函数可以在程序中多次定义,每次必须完全一致,因此通常放头文件

单例模式

定义是单例对象的类只能允许一个实例存在。

#include <iostream>
using namespace std;

class Scheduler {
	Scheduler() {}
	static Scheduler* self;

 public:
	static Scheduler* get_instance() {
		if (self == NULL) {
			self = new Scheduler;
		}
		return self;
	}
};

Scheduler* Scheduler::self = NULL;

int main() {
	Scheduler* s = Scheduler::get_instance();
	Scheduler* s2 = Scheduler::get_instance();

	cout << s << endl;
	cout << s2 << endl;

	return 0;
}

output: s和s2是同一个对象

0x10072d1d0
0x10072d1d0

template class 模板类

万能容器,代码重用


template < class T >
class Stack {
	T   pool[100];
	int top;

 public:
	void Push(T in) {
		pool[top++] = in;
	}
	T Pull() {
		return pool[--top];
	}
};

int main() {
	Stack< int > stack;
	return 0;
}

#include <vector>
  • 万能容器
  • 动态增长
#include <vector>
using namespace std;

int main(){
	vector<int> vi;
	for(int i = 0; i < 1000; i++)
		vi.push_back(i);		//数据的输入
	cout << vi[301] << endl;
	return 0;
}

数据的删除

vi.pop_back(); //将数组最后一个元素删除
#include <list>

运算符重载:[ ] 中括号被重载过

cout << vi[301] << endl;

list来一块分一块(空间),vector会先分配一块,不够的话找一块更大的空间并且把原先的搬过去。

list和vector得最主要的区别在于vector使用连续内存存储的,他支持[]运算符,而list是以链表形式实现的,不支持[]

list : 双向链表
每一个结点都包括一个信息快Info、一个前驱指针Pre、一个后驱指针Post。

迭代器模式
  • 一个类内的类:iterator。访问vector, list各种容器。
  • 解决统一访问问题。
	vector<int> vi;
	for(int i = 0; i < 1000; i++)
		vi.push_back(i);
	vector<int> :: iterator it = vi.begin()// 声明一个迭代器类
	while(it != vi.end()){
		cout << *it << endl; 				// 重载了 * 运算符
		it++;			
	}

char* 和 char[]

char*存在字符常量区,是不能修改的。
char[]已经分配了内存,是可以改变的。
此外:s1 = &s[0];*s1 = s[0];是不一样的

int main() {
	char  s[] = "string";
	char* s1;
	s1 = &s[1];
	cout << s1 << endl;
}

output:
tring

int main() {
	char  s[] = "string";
	char* s1;
	*s1 = s[0];
	cout << s1 << endl;
}

output:
s

函数指针

#include <iostream>
using namespace std;

string lengthCompare(const string& s1, const string& s2) {
	return s1.size() <= s2.size() ? s1 : s2;
}

int main() {
	char*  s1 = "hello";
	char*  s2 = "hell";
	string str = lengthCompare(s1, s2);
	cout << str << endl;

	string (*pf)(const string&, const string&);
	pf = lengthCompare; // pf = &lengthCompare; // 一个等价的写法
	string str1 = (*pf)(s1, s2);
	// string str1 = pf(s1,s2); // 一个等价的调用
	cout << str1 << endl;
}

考题

观察者模式,工厂模式,单例模式。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值