命名空间

namespace NameSpace{},为解决变量以及函数重名而出现;

只能在全局中声明,可以嵌套,使用NameSpace::Items,调用内容;

可以在函数开头用 using NameSpace::Items,来声明引用内容,或者用 using namespace NameSpace,来声明引用全部内容;

可以匿名,引用内容则为::Items,等效于全局内容;

引用

定义的时候就需要赋值:int &ra = a; ,前面加地址符号,意义是使得ra和a共享地址(取个别名);

数组定义时记得加括号明确计算意义:int (&ra)[10] = a[10]

类似指针,函数传参时可以间接影响外部变量,并且返回时不能返回局部变量,因为是一个地址,内容会发生改变;

本质

int &ra = a 等价于 int * const ra = &a

目的为了简化源码理解,不需要构造多级指针,传参时传入引用可以体现出改变外部变量的意思;

面向对象与类

特征:封装,继承,多态;

封装

将一类的函数和数据装在一个类里,设置私有数据域,和公共方法称为封装

结构体等同于类,类里的对象拥有属性(数据),行为(函数),以及构造函数(初始化数据的函数,与类同名无返回);

初始化定义:声明同时赋值;

1
2
//构造函数后面加冒号变量为初始化,意义在于控制常量
matrix(int input):line(input)

析构函数:与类名同名,前面加~,局部变量返回时自动执行,一般做扫尾工作,如free;

调用类函数的时候,实际上编译器将外部变量的引用(匿名 / this )传入了类函数中;

定义在类中的函数,可以使用 this 指针,这个指针用于操控类里的变量,防止同名冲突;

继承

将一个类的内容添加到另一个类的起始;

1
2
3
4
5
6
7
8
9
10
11
12
class Father
{
public:
int same;
int age;
}
class Child:Father //继承
{
public:
int same;
int height;
}

相当于:

1
2
3
4
5
6
7
8
class Child
{
public:
int same;
int age;
int same;
int height;
}

可以通过父类引用访问子类,但只能指到age结束,要访问子类后续可以用操作指针;

同样可以通过子类引用访问父类,但需要强制转换,且指针可以访问父类后面的空白区域;

1
2
3
4
5
6
7
Child ch;
Father *pfa = &ch;
pfa->age; //通过父类指针访问继承age;
---
Father fa;
Child *pch = (Child *)&fa;
pch->age; //通过子类指针访问父类age;

若继承里有同名变量,则使用就近原则使用子类的内容;若要访问父类的同名变量,加上父类名:

1
2
3
Child a;
a.same = 1; //改变原子类内容
a.Father::same = 2; //改变继承内容

C++拥有多继承,按顺序排的类,哪个在前,其内容内存地址继承在最上方;

权限继承:

class修饰默认为private,若以public继承,则父类内容里保持不变(public还是public,private还是private);

private修饰的内容继承,子类不能访问,但protected可以;(protected和private就只有这个区别)

构造析构函数也会继承;

new&delete

创建类指针时,在堆开辟类内存空间并执行构造函数: Child *pch = new Child(); 类似于java;

使用delete释放空间并执行析构函数: delete pch; ;

拷贝构造函数

1
2
3
4
Test::Test(Test &testaddr)
{

}

用于对象,复制属性时候执行,只是浅拷贝,值转换,指针可能出错;一般对象作为参数和返回值时就会调用;

虚继承

用于避免多继承的多义变量产生;如:A -> B,A -> C;B,C -> D;使得D里有两组A内容;

继承时,在继承类的前面用 virtual 修饰,称其为虚基类;此时,虚基类不会直接继承其内容给子类,而是会给子类一个虚基类表,这是一个指针,指向两个数据,第一个表示虚基类表位于当前所在类的偏移,第二个表示继承父类位于虚基类表的偏移;

一般虚继承的父类内容放在子类内存的下方;

友元

在类中定义,用 friend 修饰,可以为另类和函数,使其能够直接使用 private 修饰的内容;

运算符重载

类类型的对象进行运算是没有意义的,所以可以自己给运算符定义意义;

使用以下内容重载运算符 加号,使得当两个对象相加时执行 “+” 函数的语句:

1
2
3
4
5
6
7
8
9
class A
{

}

A operator+ (A a1, A a2)
{

}

多态

明确一些专有名词:

  1. 重载:同一类里,同名函数,不同参数表;
  2. 重写:不同类,且类有继承关系,同名函数;
  3. 静态联编:程序编译时定死函数符号以及类指针的引用;动态联编则相反;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A
{
public:
void HaHa()
{
cout<<"233";
}
}
class B:public A
{
public:
void HaHa()
{
cout<<"lol";
}
}

---
//此为静态联编,当 *p 定为 A 类型时,默认A -> HaHa() 调用 A 类里的函数;则两个都输出 233 ;
//当父类A里函数用 virtual 修饰时为动态联编,两个分别输出 233 和 lol;

A a;
B b;
A *p = NULL;
A = &a;
A -> HaHa();
A = &b;
A -> HaHa();

可以抽象地将函数理解成是存在于类中(实际上并没有),继承后盖在子类头上,一共有两个HaHa函数,就近原则选择父类的函数(父类指针),而虚函数的声明免除了这个误会,就类似于虚继承了;

同一个指针,指向不同对象,展现出不同效果的情况,称其为多态,为了实现多态而不产生歧义,才有的虚函数

定义:一个类中声明了虚函数,但是没给出实现,此称为纯虚函数,这个类叫做抽象类;抽象类不配有对象;

1
2
3
4
class A
{
virtual void func() = 0;
}

Q:为什么这么做?

A:用于构造模型,用子类去实现具体内容,并由一个指向父类的指针去实现多个子类的多态;

如果定义了虚函数,则类里会存在虚表指针独占内存,指向虚表,虚表里包含各个虚函数的地址;

继承会合并父类虚表为一个,如果是多继承,则合并第一个继承的虚表,后面的会保留下来;

模板

为解决多次重载不同**类型(重点)**参数的函数而出现;

1
2
3
4
5
template<typename DD>
DD func(DD a, DD b)
{
return a-b;
}

使用时,直接代入函数就行,DD需同类型,但不能指针;

需要指针时,需要使模板特化

1
2
3
4
5
6
7
//紧跟上面的内容写
---
template<>
int *func<int *>(int *a, int*b) //尖括号内容可有可无
{
return *(a + b);
}

如果有重载函数,模板特化,函数模板都满足使用的类型,则优先级由最特化到最不特化;

类也有模板,用 template<class DD> 声明;DD则可替换类里属性的类型;

使用时,用 ClassName<type> a; 来创建一个类对象;

模板的机制:

实际上,模板通过把一个数据类型用通用参数符号来代替,实际使用时,用某种数据类型进行替换;

达到处理类型不同,实现功能相同的效果;

模板技术成为泛型编程

异常处理

三个关键词:

throw : 手动抛出异常,一般满足if关系式后执行,也一般存在try包括中;

1
throw code;		//code为之后的捕获catch返回的内容

trycatch 成对出现,前者会用大括号包住可能出现异常的语句块;后者作为函数跟随: catch(type code) , 接收抛出的code,并执行catch函数里的内容;

catch里也能放 ... ,意思是捕获任意类型(包括类)异常(接收任意类型code);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int func(int a, int b)
{
if(b == 0)
{
throw 12;
}
return a/b;
}

try
{
int a = func(1,0);
}
catch(int code)
{
cout << "异常了,且code为" << code << endl;
}

上面代码会使得打印catch中的语句,且code值为12;

STL

标准模板库;

其部件:容器,算法,迭代器;目的是为了更好地存储数据(如排序和查找遍历)

容器

顺序容器:

  • Vector:向量,动态数组

    可扩大数组,每次扩大自身2倍;

    用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    push_back	//插入到末尾
    insert //指定位置插入
    Pop_back //删末尾元素
    erase //删除指定位置
    Clear //清空所有
    Vec[i] //访问索引元素
    at //返回指定下标处元素
    begin //返回 iterator 指向第一个元素
    End //返回 iterator 指向末尾下一个
    empty //是否为空?
    size //获取元素个数
    swap //交换两个元素

    ---
    vector<type> item; //创建
    vector::iterator it = item.begin(); //创建迭代器(指针)
  • String:字符串

  • List:双向链表

    不能操控下标,只能添加和删除以及遍历;

    用法和维克托差不多,多了个对头的操作:

    1
    2
    3
    4
    5
    6
    Push_front		//插入头部
    Pop_front //头部删除

    ---
    list<type> item; //创建
    list::iterator it = item.begin(); //创建迭代器(指针)

    若要操作插入,需要创建迭代器,并且用for循环让iterator++,对list而言,迭代器不能直接加一个数;

  • Deque:双端队列

关联容器:

  • Set

  • Map

    类似于python字典,有 key 和 value,其类型位pair;

    可以用 map[key] = value 实现赋值;

    用法:

    1
    2
    3
    4
    5
    6
    7
    using namespace std;

    map<const char*, int> item;
    item.insert(make_pair("BC",12));
    map::iterator it = item.begin();
    it -> first //访问key值
    it -> second//访问value值
  • Multiset

  • Multimap

容器适配器:

  • Stack
  • Queue
  • Prority queue

接口的概念

面向对象的过程中,接口就是公共属性的函数,是类内部私有属性和用户的桥梁;

多态的解释中,接口是抽象类函数,运用这个接口去实现不同子类的多态;

总结

实际学下来花了接近2个星期吧,说多不多说少不少,要真的掌握和吃牢固还是比较难受;

这么多内容实际上和C的差别也就是编译器,底层展现的代码其实都差不多(比如引用),按java的说法,其实对于这类语言还有更多说法,比如类里定义另类型对象这种聚集关系,以及链式异常(两种不同嵌套);

关于多态的思考

类型变量实际上也是一个引用,只不过CPP是声明即创建,只不过没赋值,而java是需要声明和创建的;所以导致一个结果:java只需要声明后创建子类类型就能实现多态,而CPP需要创建一个类指针来接收子类引用;

关于类的思考

STL的思想就很像java,感觉STL在往java和python靠,把一些容器的实现都弄成类,而且是泛型的;但是用之前记得调用头文件和std命名空间;