1、引用类型
引用类型是C++引入的新类型,根据汇编的知识进行理解,程序在汇编后,变量名将失去意义,因为汇编码将替换成用内存的(链接地址or运行地址)访问变量。在C/C++语言中,用变量名表示变量所占的那块内存,为了使多个名称绑定一个变量,C++引入了引用类型。其主要目的就是让变量名和变量能够真正的区分开,C语言中变量名和变量是1对1的,是分不开的。
int i = 1; //栈中分配sizeof大小内存,内存里的值为1,i <=> 内存的标号。
int &ri = i; //给变量取一个别名ri,ri <=> i。
注意:定义引用,并不是定义变量,因为定义引用是不开辟空间的。可以理解为定义变量名,所以它必须初始化,必须与变量绑定起来。
2、const限定符
const 对象必须初始化:
const int j = 42; //正确(编译时初始化)
const int i = get_size(); //正确(运行时初始化)
const int i; //错误
const 与普通类型的组合:
const int 与int const是等价的
const float 与 float const是等价的
。。。。
const 与指针类型组合(仅指针):
int c1 = 0;
const int c2 = 0;
const int *p1 = &c1; //左侧const限定右边的变量,即c1不能改变,限定右边的叫低层const
int * const p2 = &c2; //右侧const限定左边的变量,即p2不能改变,限定左边的叫顶层const
const int *const p3 = p2; //左右边的值都被限定,都不能改变
const 与引用的组合:
const int &ri = c2; //仅此一种写法,且只限定右边的变量
注意:const限定的都是变量,无论左边还是右边的变量都是左值,它并不针对右值。
注意:左值和右值不是根据在 = 号左的右来区分的(百度上有很多根据等于号这样的错误说法)
3、命名空间的写法
写法一:在cpp文件中,每个变量的定义都加上命名空间的名称。
写法二:在cpp文件中,每个变量的定义都用namespace框起来。
4、泛型编程的基础=>模板
0、面向对象编程和泛型编程都是一种编程范式。
1、C++ Primer(第五版):面向对象编程(OPP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:
OPP能处理类型在程序运行之前都未知的情况。(运行时知类型)
泛型编程中,编译时就能获得其类型了。(编译时知类型)
2、模板和宏定义很像,但是二者不同,例如:宏定义有直接替换而不检查的缺陷,而模板没有此缺陷。具体可以参考匿名类或结构体的使用。(类似但不同,模板更安全=>非替换=>根据参数反推实例化类型)
3、编译过程无法区分类是不是被实例化过:所以会被编译进可执行文件中。
编译过程可以区分类模板是不是被实例化过:所以会选择性的编译进可执行文件中。(类模板的转换发生在编译期间,如何编译期间未被实例化过,就不会被编译进可执行文件中)
4、 类定义==实例化==>对象 (类的实例化发生在运行期间)
类模板定义==实例化==>类定义==实例化==>对象 (类模板的实例化发生在编译期间)
5、C++标准库(STL)之顺序型容器
vector:可变大小数组
deque:双端队列
list:双向链表
forward_list:单向链表
array:固定大小数组
string:与vector相似,但专门用于保存字符
=====>顺序容器适配器
stack:栈适配器,默认情况下基于dequeue实现
queue:队列适配器,默认情况下基于dequeue实现
priority_queue:优先队列适配器,默认情况下基于vector实现
6、C++标准库(STL)之关联性容器
=====>按关键字有序保存
map:关联数组
set:关键字即值,只保存关键字的容器
multimap:关键字可重复出现的map
multiset:关键字可重复出现的set
=====>关键字无序保存
unordered_map:用哈希函数组织的map
unordered_set:用哈希函数组织的set
unordered_multimap:哈希组织的map;关键字可以重复出现
unordered_multiset:哈希组织的set;关键字可以重复出现
总结:宏定义可以摆脱变量类型,但不具备类型的检查。模板不仅可以拜托变量类型,还可以进行类型的检查。泛型编程作用举例:函数重载时,函数逻辑相同,但形参类型不同,要写不同的函数,有了函数模板,只需要写一个函数就行了。
//vector使用示例========================================
int main()
{
std::vector<int> vec = { 1, 2, 3 };
vec.insert(vec.begin(), 11);
vec.push_back(99);
//vector<int>::iterator it = vec.begin();
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << std::endl;
}
std::cout << "<======>" << std::endl;
for (auto& x : vec) {
std::cout << x << std::endl;
}
return 0;
}
5、OOP的三大特征之类的封装
0、这里主要说明类的构造函数和初始化参数列表。
1、类的缺省构造函数
#include <iostream>
class Point{
public:
int x;
int y;
};
int main(int argc,char* argv[]){
Point pt; //调用缺省构造函数
//Point pt();
pt.x = 0;
pt.y = 1;
return 0;
}
==>说明:在不定义构造函数,会生成一个缺省构造函数,如下所示
class Point{
public:
Point(){
//一些默认初始化操作
};
int x;
int y;
};
2、一旦你定义构造函数,将不再生成缺省构造函数
#include <iostream>
class Point {
public:
Point(int j, int k) {
this->x = j;
this->y = k;
}
int x;
int y;
};
int main(int argc, char* argv[]) {
//Point pt; //错误,没有缺省构造函数
Point pt(5, 5); //正确
pt.x = 0;
pt.y = 1;
std::cout << pt.x << std::endl;
std::cout << pt.y << std::endl;
return 0;
}
=>说明:C++11允许我们使用=default来生成一个默认的构造函数
class Point {
public:
Point() = default;
Point(int j, int k) {
this->x = j;
this->y = k;
}
int x;
int y;
};
3、初始化参数列表
1、定义时初始化
class Point {
public:
Point() = default;
Point(int _x, int _y) : x(_x), y(_y) {
}
int x = 0;
int y = 0;
};
2、定义后初始化
class Point {
public:
Point() = default;
Point(int _x, int _y){
this->x = _x;
this->y = _y;
}
int x = 0;
int y = 0;
};
=>总结:初始化列表:
对类的成员进行:定义时初始化
初始化列表仅适用于构造函数(初始化列表是初始化成员变量的)
6、OOP的三大特征之类的继承
0、这里主要说明父类的构造函数和构造函数执行的顺序。
1、父类无缺省构造函数时,要显式调用父类的构造函数:
#include <iostream>
using namespace std;
class A {
public:
A(int y) {
std::cout << "A()" << std::endl;
}
~A() {
std::cout << "~A()" << std::endl;
}
};
class Point: public A{
public:
Point(int j, int k):A(3){ //显式调用父类的构造函数
this->x = j;
this->y = k;
std::cout << "Point(int j, int k)" << std::endl;
}
int x;
int y;
};
int main(int argc, char* argv[]) {
Point pt(1, 2);
return 0;
}
2、C++的初始化方式:直接初始化、拷贝初始化
直接初始化: =>调用构造函数的初始化
int i(5); //类中不可用
int i{5};
int i = int(5);
拷贝初始化: =>调用拷贝函数的初始化
int i = 1; //需要注意的是,这里发生了强制转换(直接初始化不存在强制转换问题)
需要注意的是:类是C++的相对于C的新增部分,其在类内成员变量初始化问题上,有一个规定,即类内部的()全被解释为函数调用,所以类内只能用=和{}进行初始化,而不能使用()。
3、基类的实例化顺序:
1、实例化成员变量。
2、执行类的构造函数。
4、子类的实例化顺序:先按继承顺序实例化每个父类,最后再实例化子类。(析构相反)
基类实例化 => 子类实例化 => 子类析构 => 基类析构
示例程序:
#include <iostream>
using namespace std;
class A {
public:
A(int a) { cout << "A():id = "<< this << endl; }
~A() { cout << "~A():id = " << this << endl; }
};
class B {
public:
B(int b) { cout << "B():id = " << this << endl; }
~B() { cout << "~B():id = " << this << endl; }
A obj{ 2 };
};
class Point :public B, A {
public:
Point(int x, int y):A(2),B(3) { cout << "Point():id = " << this << endl; }
~Point() { cout << "~Point():id = " << this << endl; }
};
int main(int argc, char* argv[]) {
Point pt(5, 5);
return 0;
}
打印如下: 先实例化B => 再实例化A => 最后实例化Point
A():id = 005AFBE4 // B
B():id = 005AFBE4 // B
A():id = 005AFBE6 // A
Point():id = 005AFBE4 //Point
~Point():id = 005AFBE4
~A():id = 005AFBE6
~B():id = 005AFBE4
~A():id = 005AFBE4
8、OOP的三大特征之类的多态
0、C++允许基类类型的指针指向子类的对象。
1、C++中,主要有两种类型的多态性:
编译时多态性(静态多态性):
函数重载、运算符重载、模板。
运行时多态性(动态多态性):
虚函数、多态继承。
2、主要是动态多态性:同一操作总用于不同的对象,可以有不同的执行结果
实现方式:基类的虚函数、子类函数重写(覆盖)、基类指针
关键字:基类中定义virtual函数、子类中重写并添加override,(override可以不加,但建议加上,因为加上override不仅可以进行规则检查,还可以增加代码的可读性)
#include <iostream>
class Shape {
public:
virtual void draw() {
std::cout << "Drawing shape...\n";
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing rectangle...\n";
}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing circle...\n";
}
};
int main() {
Shape* shapes[2];
shapes[0] = new Rectangle();
shapes[1] = new Circle();
for (int i = 0; i < 2; i++) {
shapes[i]->draw();
}
return 0;
}
3、抽象类:除了使用虚函数,C++ 中还可以使用抽象类实现多态。抽象类是一种不能被实例化的类,它通常包含至少一个纯虚函数,纯虚函数在基类中声明时没有函数体,在派生类中必须被重写。由于抽象类不能被实例化,因此派生类必须实现所有纯虚函数才能被实例化
#include <iostream>
class Shape {
public:
virtual void draw() = 0;
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing rectangle...\n";
}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing circle...\n";
}
};
int main() {
Shape* shapes[2];
shapes[0] = new Rectangle();
shapes[1] = new Circle();
for (int i = 0; i < 2; i++) {
shapes[i]->draw();
}
return 0;
}
9、初始化与赋值的区别
初始化的含义是在创建对象时赋予一个初值。
赋值的含义是将对象的当前值擦除掉,以一个新值代替。
区分方式:在对象创建时赋予值,叫初始化,否则即为赋值。
C++语言的初始化方式(以类类型为例):
<1>:直接初始化:调用的是与实参匹配的构造函数
<2>:拷贝初始化:调用的是拷贝构造函数