指针
内存是计算机的一切
指针对管理和操纵那个内存十分重要
什么是指针?
指针是一个整数,一个数字,它存储一个内存地址。它只是一个在内存中存放地址的整数,如果我们给一个指针一个类型,只是表明在那个地址的数据可能是我们给它的类型,我们写出来,可以让我们从语法上来说更容易理解。总之,一个类型,不会改变一个指针,指针永远都只是一个整数,一个内存地址。
类型对于操作这块内存有用,如果我想读取或者写入它,内存类型可以帮助我,因为编译器会知道。比如,一个int型是4字节,所以当我们要设置一个值的时候,它会设置4个字节内存。
总结:指针只是一个存储着内存地址的整数
#include<iostream>
using namespace std;
int main()
{
int* ptr = nullptr;
int val = 8;
ptr = &val;
//逆向引用指针,现在可以访问指针中存的那块内存中的数据,可以向那块地址读取或者写入数据
*ptr = 1;
//分配8字节的内存,并返回一个指向这块内存的开始地址的指针
char* buffer = new char[8]; //new 关键字,在 堆 中请求内存
char** bufptr = &buffer; //实际上,指针也是一个整数
memset(buffer,0,8); //用我们指定的数据填充一块内存
delete[] buffer;
return 0;
}
引用(Reference)
引用只是指针的一个扩展。
引用必须引用一个已经存在的变量,引用本身并不是一个新的变量,它并不真正占用内存。而指针是一个实实在在的整型变量。
没有任何事情是引用能做但指针不能做的,用好引用可以让代码变得干净和简单很多,更有可读性,不会像指针那样让程序很难懂。
一旦你声明了一个引用,你就不能更改它所引用的对象 \textcolor{red}{一旦你声明了一个引用,你就不能更改它所引用的对象} 一旦你声明了一个引用,你就不能更改它所引用的对象
而我们声明了指针后,指针是可以改变指向的对象的。
在声明引用时,必须初始化,不能只声明不赋值。
#include<iostream>
#define LOG(x) cout << x << endl
using namespace std;
void Increment1(int value)
{
value++;
}
void Increment2(int* value)
{
(*value)++;
}
void Increment3(int& value)
{
value++;
}
int main()
{
int a = 5;
int b = 6;
/*
传值调用,在调用该函数时,程序将会拷贝参数值 5 到这个函数里,直接拷贝
这将会创造一个全新的变量value,调用完后会发现,这里a没有增加,只是value的值增加了
*/
Increment1(a);
LOG(a);
/*
直接将需要增加的变量的地址传入函数
*/
Increment2(&a);
LOG(a);
/*
调用此函数时,会声明一个引用,即等价于:
int& value = a;
value++;
*/
Increment3(a);
LOG(a);
int& ref = a; //任何情况下,ref就是a,我们只是给a创建了一个别名
/*
一旦你声明了一个引用,你就不能更改它所引用的对象。下面这种做法的最终结果:
a = 6;
故,因为这种特性,在声明引用时,必须初始化,不能只声明不赋值
*/
ref = b;
//int& ref2; //错误
LOG(ref);
return 0;
}
小结:指针和引用的区别
- 引用被创建的同时必须被初始化,而指针可以在创建后的任何时候初始化
- 不能有NULL引用,引用必须与合法的存储单元关联。而指针是可以为NULL
- 一旦引用被初始化,就不能改变引用的关系。而指针则可以随时改变所指的对象
类基础(class)
事实上,C++支持,面向过程、基于对象、面向对象、泛型编程 四种编程风格。
简而言之,类 是一种将数据和函数组织在一起的方式。类基本上就是我们创建的一个新的变量类型
例子:在制作游戏时,常常需要创建一个玩家角色,此角色有位置(x,y)、速度,如果我们直接用变量来存储这些数据,当有1个甚至几千个角色时,创建起来就会很困难、很乱了,这样会产生很多代码,并且难以维护下去。此时,我们可以用类来简化这一步,我们可以为玩家创建一个类,叫Player,一次性包含所有的数据,最终作为一个类型。
访问控制:当你创建一个类时,你可以指定类中成员的可见性,默认情况下类中的成员访问控制都是私有的, 意味着只有类内部的函数才能访问这些变量 \textcolor{red}{意味着只有类内部的函数才能访问这些变量} 意味着只有类内部的函数才能访问这些变量
类中的函数称作 方法
小结:本质上将,类就是能使我们能对变量进行组织,变成一个类型,还为这些变量添加了函数
#include<iostream>
#define LOG(x) cout << x << endl
using namespace std;
class Player {
public:
int x, y;
int speed;
void Move(int xa, int ya)
{
x += xa * speed;
y += ya * speed;
}
};
/*
我们可以将函数,移到类中,这样可以不用传入player对象了,每个对象都会拥有自己的Move函数
当我们为特定的类调用Move函数时,就是调用他自己的,这和类外的没有什么区别,但他确实让代码
看起来简洁了不少,且易于维护
*/
void Move(Player& player,int xa,int ya)
{
player.x += xa * player.speed;
player.y += ya * player.speed;
}
int main()
{
Player player1; //实例化一个 Player 对象
player1.x = 0;
player1.y = 0;
player1.Move(1, 1);
return 0;
}
class VS struct in C++
C++中结构体和类有什么区别?什么时候该用结构体?什么时候又该用类?
答:基本没有区别。只是类中的成员默认情况下是私有的,而结构体中的成员默认情况下是公有的(这是唯一的区别)。
结构体在C++中存在的唯一原因就是它想要维持与C之间的兼容性,因为C中没有类。
编程风格:
- 当我们谈论 plain old data或者只是包含一些变量的结构时,建议用struct。如:数学中的向量类
- 结构体尽量就是数据的结构体
- 如果想要有很多的功能,像游戏里的一个玩家,或者其他可能继承的东西,建议使用class
How to writer a C++ classs
日志管理信息系统(简易版)
功能:
- 向控制台发送文本
- 控制我们发送给控制台的日志信息等级(error , warning , message)
- 如果是message等级,则三种信息都要打印;如果是warning等级,则只打印error和warning信息;如果是error等级,则只打印error信息
#include<iostream>
using namespace std;
class Log
{
/*
* 使用两部分public,将公有方法和公有成员变量分开
*/
public:
const int LogLevelError = 0;
const int LogLevelWarning = 1;
const int LogLevelInfo = 2;
private:
/*
* 用一个 m 的前缀,代表这是一个私有成员变量
* 在后面的使用过程中可以很清晰的知道那些是成员变量,那些是局部变量
* 这是良好的编程习惯
*/
int m_LogLevel = LogLevelInfo;
public:
void SetLevel(int level)
{
m_LogLevel = level;
}
void Error(const char* message)
{
if (m_LogLevel >= LogLevelError)
cout << "[ERROR]: " << message << endl;
}
void Warn(const char* message)
{
if(m_LogLevel >= LogLevelWarning)
cout << "[WARNING]: " << message << endl;
}
void Info(const char* message)
{
if (m_LogLevel >= LogLevelInfo)
cout << "[INFO]: " << message << endl;
}
};
int main()
{
Log log;
log.SetLevel(log.LogLevelWarning);
log.Error("Hello!");
log.Warn("Hello!");
log.Info("Hello!");
cin.get();
}
static 关键字
C++中的static根据上下文有两种意思:
- 在类class或者结构体struct外使用static
- 在类class或者结构体struct里使用static
类或者结构体外的static修饰的符号在链接阶段是局部的,也就是它只对定义它的编译单元(.obj)可见。而类或者结构体里的static表示这部分内存是这个类的所有实例共享的,简单来说,就算实例化了很多次这个类或者结构体,但那个静态变量只会有一个实例,类里面的静态方法也是,静态方法里没有该实例的指针(this)
本小节仅研究类或者结构体外的static修饰符
理解“类或者结构体外的static修饰的符号在链接阶段是局部的,也就是它只对定义它的编译单元(.obj)可见”
实例:
情形一,有两个cpp文件:
//main.cpp
#include<iostream>
using namespace std;
int s_Variable = 10;
int main()
{
cout << s_Variable << endl;
cin.get();
}
//Static.cpp
//static 表示在链接的时候只在这个编译单元(.obj)可见
static int s_Variable = 5; //加上s前缀表明是 静态变量
可以编译通过。这有点像 class 里面的private成员,其他的编译单元不能访问s_Variable,链接器在全局作用域下找不到它。
情形二,同样两个cpp文件,去掉Static.cpp里面的static修饰符,会报链接错误:
两个全局变量的名字是不能一样的,一种解决方法是把main.cpp中的s_Variable变量变成另一个的引用(去掉赋值,在前面加上extern关键字)
//main.cpp
#include<iostream>
using namespace std;
extern int s_Variable;//这样将在另外的编译单元中找到s_Variable的定义
int main()
{
cout << s_Variable << endl;
cin.get();
}
//Static.cpp
int s_Variable = 5;
函数用static修饰,是同样的效果。
类class或者结构体struct外,static 的全部含义:当你在class或者struct外定义一个静态变量(函数),这意味着 你定义的变量(函数)是只对声明它的cpp文件(编译单元)可见的。
重点是:尽量让全局函数和变量都用static标记,除非它们必须要用在其他的编译单元里
static for class and struct
如果你在内部定义一个static变量,这意味着这个类的所有实例中,这个变量只有一个实例。比如:创建一个Entity类,然后我不断创建这个类的实例,但是那个static变量仍然只会有一个,这意味着如果一个实例改变了这个static变量,这个改变会体现在所有的类实例中,就像是这个类的全局实例,静态方法也是一样,静态方法不通过类的实例就可以调用(使用类名加范围解析符号::就可以访问),静态方法不能访问非静态变量
#include<iostream>
using namespace std;
struct Entity
{
/*
当我们把x,y设置为静态变量时,我们就把这两个变量变成在所有Entity类的实例中只有一个副本
不同实例的x,y变量指向的是同一个共享空间
当我们需要将一条信息在所有实例中共享时,这是有意义的
*/
static int x, y;
static void print()
{
cout << x << "," << y << endl;
}
};
//类的非静态方法在编译时的真正样子
static void print(Entity e)
{
cout << e.x << "," << e.y << endl;
}
//******************************
int Entity::x;
int Entity::y;
int main()
{
Entity e;
Entity::x = 2;
Entity::y = 3;
Entity e1;
//这样引用变量其实是没有意义的
/*e1.x = 5;
e1.y = 8;*/
//可以像这样引用,就像是在名叫Entity的命名空间里创建了两个变量,它们实际上不属于类了
//当我们新建类的实例的时候,它们并不会重新分配内存
Entity::x = 5;
Entity::y = 8;
Entity::print();
Entity::print();
/*
如果我们将xy改为非静态变量,print函数仍然是静态的,这时调用函数便会报错
因为静态方法是不能访问非静态变量的,原因就是 静态方法是没有实例的
本质上,类里的每一个非静态方法都会获得当前实例作为参数(this指针),而静态方法是没有这个隐藏参数的
静态方法和我们在类的外部编写的方法是一样的
*/
cin.get();
}