C++动态开辟空间的方式

malloc

int *pa = (int )malloc(sizeof(int));

malloc方法开辟的空间的返回的类型为 void*类型,需要强转为目标类型,空间中的内容为随机值。

malloc方法开辟一块空间

calloc

int pb = (int)calloc(2, sizeof(int));

calloc方法开辟”N”块空间(N等于参数一),空间中的内容为0;

new

int *a = new int; //开辟空间

delete a; //释放a

new 开辟空间相当于malloc 实际上是对malloc的一层封装,delete是对free的一层封装。

开辟的空间需要及时释放

构造方法

Stu stu1;

会隐式调用无参的构造方法。(注意不要加括号)

如果有多个构造方法,且有参数的构造方法每个参数都有默认值,那么会出现二义性

如果只有一个有参的构造函数,且有参的构造还有均有默认值,那么会调用有参的构造函数。

如果只有有参的构造函数,且参数没有默认值,会出错

2 构造函数调用时机,当对象被创建出来时。

使用explicit关键字修饰构造方法,可以阻止它们被用来执行隐式类型转换,但他们仍可以被用来进行显示类型转换。

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
class A
{
public:
int i;
A(int v = 0)
{
i = v;
}
};

void doSo(A v)
{
cout << "参数值为:" << v.i << endl;
}

void te() {
doSo(10);
A a = A(20);
doSo(a);
}

//正常输出
//参数值为:10
//参数值为:20
//构造方法加了explicit修饰符后无法通过编译,因为不能隐式的转换10成为一个A类型的对象

内存四区

  1. 代码区

    存放函数体的二进制代码,由操作系统进行管理

  2. 全局区

    存放全局变量和静态变量以及常量

  3. 栈区

    有编译器自动分配释放,存放函数的参数值,局部变量等。

  4. 堆区

    由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

内存四区的意义:不同区域存放的数据,具有不同的生命周期,给我们更大的灵活编程。

程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:

代码区:

​ 存放CPU执行的机器指令

​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要再内存中由一份代码即可。

​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。

全局区:

​ 全局变量和静态变量存放在此。

​ 全局区还包含了常量区,字符串常量和其他常量(const修饰的全局变量)也存放在此。

​ 该区域的数据在程序结束后由操作系统释放

程序运行后

栈区:

​ 由编译器自动分配和释放,存放函数的参数值,局部变量等。

​ 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int* func()
{
int a = 11; //局部变量,存放在栈区,栈区的数据在函数执行结束后被自动释放
return &a; //返回局部变量的地址
}

//程序输出函数返回值1= 11 第一次是因为编译器帮我们做了一次保留
//函数返回值2= 463968
int main()
{
int* p = func();
cout << "函数返回值1= " << *p << endl;
cout << "函数返回值2= " << *p << endl;
}

堆区:

​ 由程序员分配释放,若程序员不释放,程序结束后时由操作系统回收。

​ 在C++中主要利用new在堆区开辟内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int* func()
{
int a = new int(10); //利用new关键字 将数据开辟在堆区
return &a;
}

//程序输出函数返回值1= 10
//函数返回值2=10
int main()
{
int* p = func();
cout << "函数返回值1= " << *p << endl;
cout << "函数返回值2= " << *p << endl;
delete p;
}

文件操作

文件操作需要包含头文件

文件类型分为两种:

  1. 文本文件:文件以文本的ASCII码形式存储在计算机中

  2. 二进制文件: 文件以文本的二进制形式存储在计算机中,用户一般无法直接读懂

操作文件的三大类:

  1. ofstream 写操作
  2. ifstream 读操作
  3. fstream 读写操作

文本文件

写文件:

写文件操作步骤:

  1. 包含头文件

    #include

  2. 创建流对象

    ofstream ofs;

  3. 打开文件

    ofs.open(“文件路径”, 打开方式);

  4. 写数据

    ofs << “写入数据”;

  5. 关闭文件

    ofs.close();

文件的打开方式:(多个打开方式使用 “|” 连接)

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

读文件:

写文件操作步骤:

  1. 包含头文件

    #include

  2. 创建流对象

    ifstream ifs;

  3. 打开文件

    ifs.open(“文件路径”, 打开方式);

  4. 读数据

    四种方式读取。

  5. 关闭文件

    ifs.close();

eg:

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
29
30
31
32
33
34
int main()
{
//2创建流对象
ifstream ifs;
//3打开文件
ifs.open("./testFstream.txt", ios::in);
//4读数据
// 第一种
/*char buf[1024] = {0};
while (ifs >> buf)
{
cout << buf << endl;
}*/
////第二种
/*char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}*/
//第三种
/*string buf;
while (getline(ifs,buf))
{
cout << buf << endl;
}*/
//第四种 效率最低
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
//5关闭文件
ifs.close();
}

二进制文件

以二机制的方式对文件进行读写操作打开方式要制定为 ios::binary

写文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型: ostream& write(const char* buffer, int len);

参数解释: 字符指针buffer指向内存中一段存储空间。len是读写的字节数

eg:

二进制写对象

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
29
30
31
32
33
34
35
36
37
38
#include <fstream>
#include <string>

class Person
{
public:
char m_Name[64];
int m_Age;
};

//二进制文件 写文件
void test01()
{
//1、包含头文件

//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);

//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);

Person p = {"张三" , 18};

//4、写文件
ofs.write((const char *)&p, sizeof(p));

//5、关闭文件
ofs.close();
}

int main() {

test01();

system("pause");

return 0;
}

二进制读取写入图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	//读取图片文件并写入新文件中
ofstream ofs;
ifstream ifs;
ifs.open("./bg.jpg", ios::in | ios::binary);
ofs.open("./bg_copy.jpg", ios::out | ios::binary);

char buf[1024] = { 0 };
while (ifs.read(buf, sizeof(buf)))
{
ofs.write(buf, sizeof(buf));
}

ifs.close();
ofs.close();

读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

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
29
30
31
32
#include <fstream>
#include <string>

class Person
{
public:
char m_Name[64];
int m_Age;
};

void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}

Person p;
ifs.read((char *)&p, sizeof(p));

cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}

int main() {

test01();

system("pause");

return 0;
}
  • 文件输入流对象 可以通过read函数,以二进制方式读数据

STL标准模板库

C++一种编程思想叫做泛型编程,用的主要技术就是模板。

C++提供了两种模板机制:函数模板类模板

函数模板

作用:

建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表

语法:

1
2
template <typename T>
函数声明或定义

解释:

template –声明创建模板

typename – 表示其后面的符号是一种数据类型,可以用class代替

T – 通用的数据类型,名称可以替换,通常为大写字母

实例:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

//交换整型函数
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}

//交换浮点型函数
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}

//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}

void test01()
{
int a = 10;
int b = 20;

//swapInt(a, b);

//利用模板实现交换
//1、自动类型推导
mySwap(a, b);

//2、显示指定类型
mySwap<int>(a, b);

cout << "a = " << a << endl;
cout << "b = " << b << endl;

}

int main() {

test01();

system("pause");

return 0;
}

类模板

类模板作用:

  • 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。

语法:

1
2
template<typename T>

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

T — 通用的数据类型,名称可以替换,通常为大写字母

示例:

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
29
30
31
32
33
34
35
#include <string>
//类模板
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};

void test01()
{
// 指定NameType 为string类型,AgeType 为 int类型
Person<string, int>P1("孙悟空", 999);
P1.showPerson();
}

int main() {

test01();

system("pause");

return 0;
}

总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

C++11 中 using关键字可以替代typedef 给类型取别名:

区别: using可以在泛型中使用, typedef在泛型中使用必须借助结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename Val>
using str_map_t = std::map<std::string, Val>;


template <typename Val>
struct str_map
{
typedef std::map<std::string, Val> type;
};

// 重定义unsigned int
typedef unsigned int uint_t;
using uint_t = unsigned int;


// 重定义std::map
typedef std::map<std::string, int> map_int_t;
using map_int_t = std::map<std::string, int>;

// 重定义函数指针
typedef void (*func_t)(int, int);
using func_t = void (*)(int, int);

Type traits 类型萃取

iterator 迭代器是算法与容器之间的桥梁

如果算法需要知道操作的容器的数据类型,算法需要从迭代器中获取到五个参数

1
2
3
4
5
typedef Category	iterator_category;	// 迭代器类型
typedef T valut_type; // 迭代器所指对象类型
typedef Distance difference_type; // 两个迭代器之间的距离
typedef Pointer pointer; // 迭代器所指对象的类型指针
typedef Reference reference; // 迭代器所指对象的类型引用

算法不会直接从迭代器中获取这个五个参数信息,(比如vector没有使用迭代器二十直接使用的指针),而是使用type traits获取五个参数信息。

vector扩容:

size()和capacity()区别

size()返回vector中的实际数据的数目

capacity()返回vector的容量大小(可能大于等于size)。

默认容量是0,每次扩容是之前的1.5倍,每次扩容都会使用一块新的内存,拷贝之前的所有数据,然后添加新的数据。

const修饰类的成员函数

const修饰类成员函数,实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。

常见问题

1、面向对象的三大特性:封装、继承、多态
2、类的访问权限:private、protected、public
3、类的构造函数析构函数、赋值函数、拷贝函数
4、移动构造函数与拷贝构造函数对比
5、深拷贝与浅拷贝的区别
6、空类有哪些函数?空类的大小?
7、内存分区:全局区、堆区、栈区、常量区、代码区
8、C++与C的区别
9、struct与class的区别
10、struct内存对齐
11、new/delete与malloc/free的区别
12、内存泄露的情况
13、sizeof与strlen对比
14、指针与引用的区别
15、野指针产生与避免
16、多态:动态多态、静态多态
17、虚函数实现动态多态的原理、虚函数与纯虚函数的区别
18、继承时,父类的析构函数是否为虚函数?构造函数能不能为虚函数?为什么?
19、静态多态:重写、重载、模板
20、static关键字:修饰局部变量、全局变量、类中成员变量、类中成员函数
21、const关键字:修饰变量、指针、类对象、类中成员函数
22、extern关键字:修饰全局变量
23、volatile关键字:避免编译器指令优化
24、四种类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
25、右值引用
26、std::move函数
27、四种智能指针及底层实现:auto_ptr、unique_ptr、shared_ptr、weak_ptr
28、shared_ptr中的循环引用怎么解决?(weak_ptr)
29、vector与list比较
30、vector迭代器失效的情况
31、map与unordered_map对比
32、set与unordered_set对比
33、STL容器空间配置器