c++基础学习

c++基础

ava c# 中间有很多虚拟机来转换代码,编译完不是汇编,软件运行起来才显示汇编叫解释型语言

编译完是属于它自己的二进制格式 编译完cpu不认识,

需要把这个二进制格式翻译成二进制 20年前些游戏都是c++写 不卡嘛

编译型语言

编译型语言是指程序在执行之前需要一个专门的编译过程,把程序源文件编译为机器语言的文件,运行时不需要重新编译,执行效率高,但缺点是,编译型语言依赖编译器,跨平台性差。

举例:比如C语言程序的执行过程,要先将后缀为.c的源文件通过编译、s接为后缀为.exe的可执行文件,才能运行。所以说我们编译完的软件已经确定了cpu

但是移植性很差 想要到另外一个平台上 就需要换个cpu去编译

解释型语言

解释型语言是指源代码不需要预先进行编译,在运行时,要先进行解释再运行。解释型语言执行效率低,但跨平台性好。

举例:比如Java程序执行过程,我们写好代码,直接运行即可(运行前有解释的过程)。

编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。
解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束!

输入输出的标准

Cin wcin(宽字节) 输入流 stdin

Cout wcout 输出流 stdout

Cerr错误 stderr

Clog 日志 这个

流的概念 如果你要写文件 c语言要换代码对吧

但是如果是流的话 可以根据任何情况显示 流到哪程序显示到哪 很方便

c语言结构体没有函数

c++结构体有函数 什么叫流 不挑数据 这就是精髓

类型自动识别

把数据流到显示器上

cout << flush ; //刷新缓存 cout.flush() //也可以刷

cout<<2

这个 2也不是立马就出来的 有缓存 所以需要刷新缓存

Precision 是设置精度 包括整数 并且会四舍五入

比如123.2562323 设置精度为五 所以输出会是123.26

精度就是 整数加小数总的数量

Printf(“-20%s”)

左对齐

Printf(”20%s“) 右对齐

内联函数

1.由编译器根据代码决定是否内联

2.debug不内联,release才会内联

3.函数声明和实现都可以加内联,通常加载实现处

4.内联函数不能再.cpp和.h分开放, 实现代码必须.h中

函数重载

函数重载(overload) ==> 名称问题

什么情况下函数构造重载

1.函数作用域一致

2.函数名称相同

3.返回值类型不作为重载的依据

4.参数类型不同或者个数不同或者类型顺序不一致,构成重载

5.const参数重载识别

6.typedef类型不参与重载

运算符重载

注意,这个类似复制构造函数,将=右边的本类对象的值复制给等号左边的对象,它不属于构造函数,等号左右两边的对象必须已经被创建

// 若没有显示的写=运算符重载,则系统也会创建一个默认的=运算符重载,只做一些基本的拷贝工作

Complex &operator=(const Complex &rhs)

{

​ // 首先检测等号右边的是否就是左边的对象本,若是本对象本身,则直接返回

​ if ( this == &rhs )

​ {

​ return *this;

​ }

​ // 复制等号右边的成员到左边的对象中

​ this->m_real = rhs.m_real;

​ this->m_imag = rhs.m_imag;

​ // 把等号左边的对象再次传出

​ // 目的是为了支持连等 eg: a=b=c 系统首先运行 b=c

​ // 然后运行 a= ( b=c的返回值,这里应该是复制c值后的b对象)

​ return *this;

}};

结构体的缺点:

1.无法控制访问权限

2.传递参数麻烦

c++的类语法 就是解决这两个问题

结构体 默认访问权限是public

类默认访问权限是private

类告诉大家一个非常有效的编程手段

数据和方法的分离,改数据就改数据,改方法就改方法

this指针

通过寄存器传参

成员函数默认调用_thiscall

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。它指向当前对象,通过它可以访问当前对象的所有成员。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

this 指针是编译器默认传入的,通常都会使用ecx进行传值

成员函数都有this指针,无论是否使用

this指针不能进行++ – 等操作,不能被重新赋值

this指针不占用结构体的宽度

所以由此可得普通函数比成员函数少一个对象地址传参

构造函数

因为成员是私有的 所以我们在创造

不能初始化所以我们提供一个初始化函数

第一个问题 忘记初始化 第二个问题 重复初始化

所以我们创造了构造函数 自动帮我们创造对象

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行,构造函数的名称与类的名称是一样的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;
class Line
{
public:
Line() {
cout << "打印这个函数";
}
};
// 程序的主函数
int main()
{
Line s;

return 0;
}

如果在私有的部分 初始化一个变量 在构造函数里面初始化一个变量,调试的时候先私有部分再跳到构造函数部分。相当于合并了

该类对象被创建时,编译系统对象分配内存空间,

并自动调用该构造函数->由构造函数完成成员的初始化工作编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter()自动地初始化对象c1的m_value值设置为0

构造函数的种类

1
2
3
4
5
6
7
8
9
10
11
12
13
class Complex {     
private :
double m_real;
double m_imag;
public:
// 无参数构造函数
// 如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做
// 只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
Complex(void)
{
m_real = 0.0;
m_imag = 0.0;
}

一般构造函数(也称重载构造函数)

1
2
3
4
5
6
7
8
9
10
// 一般构造函数可以有各种参数形式,一个类可以有多个一般构造函数,前提是参数的个数或者类型不同(基于c++的重载函数原理)
// 例如:你还可以写一个 Complex( int num)的构造函数出来
// 创建对象时根据传入的参数不同调用不同的构造函数
Complex(double real, double imag)
{
​ m_real = real;
​ m_imag = imag;
}


复制构造函数(也称为拷贝构造函数)

1
2
3
4
5
6
7
8
9
10
11
  // 复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中


Complex(const Complex & c)
{
// 将对象c中的数据成员值复制过来
​ m_real = c.m_real;
​ m_img = c.m_img;

}

我们看一个例子

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
#include <iostream>  
using namespace std;
class Student
{
private:
int num;
char *name;
public:
Student();
~Student();
};

Student::Student()
{
name = new char(20);
cout << "Student" << endl;

}
Student::~Student()
{
cout << "~Student " << (int)name << endl;
delete name;
name = NULL;
}

int main()
{
{// 花括号让s1和s2变成局部对象,方便测试
Student s1;
Student s2(s1);// 复制对象
}
system("pause");
return 0;
}

执行结果:调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,这会导致什么问题呢?name指针被分配一次内存,但是程序结束时该内存却被释放了两次,会导致崩溃!

编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针name拷贝后会出现两个指针指向同一个内存空间。

所以,在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。

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
#include <iostream>  
using namespace std;

class Student
{
private:
int num;
char *name;
public:
Student();
~Student();
Student(const Student &s);//拷贝构造函数,const防止对象被改变
};

Student::Student()
{
name = new char(20);
cout << "Student" << endl;

}
Student::~Student()
{
cout << "~Student " << (int)name << endl;
delete name;
name = NULL;
}
Student::Student(const Student &s)
{
name = new char(20);
memcpy(name, s.name, strlen(s.name));
cout << "copy Student" << endl;
}

int main()
{
{// 花括号让s1和s2变成局部对象,方便测试
Student s1;
Student s2(s1);// 复制对象
}
system("pause");
return 0;
}

执行结果:调用一次构造函数,一次自定义拷贝构造函数,两次析构函数。两个对象的指针成员所指内存不同。
总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
再说几句:
当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该考虑以下两种情形:
1.当函数的参数为对象时,实参传递给形参的实际上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;
2.当函数的返回值为一个对象时,该对象实际上是函数内对象的一个拷贝,用于返回函数调用处。

类型转换构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
//,根据一个指定的类型的对象创建一个本类的对象

// 例如:下面将根据一个double类型的对象创建了一个Complex对象

Complex::Complex(double r)

{

​ m_real = r;

​ m_imag = 0.0;

}

析构函数

析构函数(destructor)是成员函数的一种,它的名字与类名相同,但前面要加~,没有参数和返回值。

个类有且仅有一个析构函数。如果定义类时没写析构函数,则编译器生成默认析构函数。如果定义了析构函数,则编译器不生成默认析构函数。析构函数在对象消亡时即自动被调用。可以定义析构函数在对象消亡前做善后工作。例如,对象如果在生存期间用 new 运算符动态分配了内存,则在各处写 delete 语句以确保程序的每条执行路径都能释放这片内存是比较麻烦的事情。有了析构函数,只要在析构函数中调用 delete 语句,就能确保对象运行中用 new 运算符分配的空间在对象消亡时被释放。例如下面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
class String{
private:
char* p;
public:
String(int n);
~String();
};
String::~String(){
delete[] p;
}
String::String(int n){
p = new char[n];
}

//指针和引用不会拷贝构造

//可以显式调用析构函数, 析构完不能调用成员函数

所以提供了析构函数用来释放函数

构造析构函数核心

数据的初始化问题 以及释放问题

怎么解决资源的自动初始化和自动释放

可以避免内存泄漏

那内存泄漏是什么

这张图的内存已经泄漏了,泄漏不是指free没写 是写了没机会执行

一般来说 资源泄漏当我们关闭程序的时候,系统自动回收内存

但是游戏的服务器呢 总不能关了再开

世界上最简单的病毒Windows

Windows 32位程序如果单纯看地址空间能有4G左右的内存可用,不过实际上系统会把其中2G的地址留给内核使用,所以你的程序最大能用2G的内存。除去其他开销,你能用malloc申请到的内存只有1.9G左右。

析构是资源释放 不止释放内存

构造有参数 就报错,因为没有默认构造 不是实例化对象 不会调用拷贝构造

/*

构造析构顺序

1.同作用域的多个对象,先定义的先构造

2.同作用域的多个对象,先定义的后析构

*/

比如说 date1 作用域是最长的 所以最后释放

假设date1后面有代码 date4这个时候还没创建


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1204342476@qq.com

💰

×

Help us with donation