当前位置: 首页 > news >正文

Secure Coding in C and C ++ (三)关于语法与指针的感悟

上一篇文章中,我们讲了一些关于编译和链接以及基础的C++知识。详情请见文章
SecureCoding in C and C++(二)
本篇文章将从循环开始写起

1 循环

1. 1 for

先来个简单的例子:
打印hello world 五次:
在这里插入图片描述
很简单的吧
for循环的语法是这样的:

for (变量名,对变量的限制,变量自身的变化)

实际上,for循环先对第一个参数进行运行,比如常用的int i随后在for循环结束后进行评估,来确定第二个参数的bool值,随后执行最后的自身变化。
当然,只要你的变量在一开始声明,也不必要在for循环的第一个参数内声明:
如:
在这里插入图片描述
当然,我们也可以对其第二个参数进行提前的声明:
在这里插入图片描述
只不过这个时候对循环是否可以进行下去的判断逻辑就到了循环内部里面了。
所以还是跟我的上一篇文章一样,C++很自由,不要被传统的约定俗成的所限制,要大胆发挥自己的想象力!

1.2 while循环

while循环的语法是这样的:

while (bool)

只要括号里面的条件一直被满足,则一直进行下去。在这里插入图片描述
那么对于两种循环来说,如何去选择呢?
假设你正在做一款游戏,这款游戏有一个功能是一直需要运行的,如玩家的框体,一直处于running状态,这个时候就选择while
当我们在处理定长数组时,就选择使用for循环,因为for循环可以对循环的次数和情况做出更加精细化的处理。

1.3 do while(不是很常用)

语法:

 do {<#statements#>} while (<#condition#>);

例如:
在这里插入图片描述

2.控制流语句

2.1 continue

这里使用for循环来掩饰 continue语句
continue将跳到for循环的下一迭代。
示例:
在这里插入图片描述
如果i可以%2=0,那么就跳过这个循环,进行下一次循环。
因为我们的i是从1开始的,所以结果应该会打印 1,3,5
因为2,4可以%2=0,所以这里就进行了下一次循环。
结果如图:
在这里插入图片描述
进一步去理解一下:
我们改写这样:
在这里插入图片描述
结果显而易见了。不大于2的时候会执行Log打印。
在这里插入图片描述

2.2 break

就跟单词意思一样,休息一下
这里我们使用第一个例子:
在这里插入图片描述
当我们的i为2的时候,if判断为true 执行了break ,让循环休息了
所以这里指打印1,一点也不奇怪。
在这里插入图片描述
ok,让我们稍微break一下

2.3 return

对于控制流语句而言,return可以在任何语句中使用,可以在函数中用来返回值/或者其他东西
这里就不再详细的讲解了。
跳过,把篇幅给比较重要的下一个章节 -指针

3. 指针

3.1 指针的概念

这里讲解的指针式原始指针,对于智能指针来说,后续会讲解。
指针其实很简单,一句话吧:指针是一个整数,是一种储存内存地址的数字,就是这样。指针就是地址。就是这样
指针的类型并不会改变指针的实质,不管是int还是其他的,实质就是:指针只是一个内存地址。
例如:

void*ptr =NULL

在这里定义一个空指针。
NULL的含义其实就是

#define NULL 0

是的,这里就是让定义的指针ptr 为空指针,也就是内存地址为0.
也可以这样写:

void * ptr = nullptr;

这里定义了空指针。
定义格式:

type * name 

让我们来做一个简单的练习:
在一个变量前面加入一个 “&“符号,表示去取这个变量所在内存的地址:
在这里插入图片描述
这里使用&去取整形变量var的地址值,给指针ptr
下断点debug我们可以得知:
在这里插入图片描述
在这里插入图片描述

ptr内存储的就是变量var所在的地址。
在这里插入图片描述
好的,那么我们清楚了,如何去取某一变量的值到指针中,是“&”
那么怎么去逆向的使用指针呢?如何利用指针去修改他呢?
使用“
是的,就是一对正反操作:
这里我们来试验一下:
在这里插入图片描述
报错咯,因为我们在定义指针ptr的时候,是按照空指针去定义的。所以这里我们去写数值的时候,得把指针类型改为int才行。
这里做如下修改:
在这里插入图片描述
运行结果理所当然是12
让我们去进一步的查看这个过程,在var和
ptr处下断点:
一开始,var的内存地址和其值如图所示
在这里插入图片描述
随后执行到var =8时,
内存里变为
在这里插入图片描述
这里还可以去印证一件事情:
ptr的内存和*ptr的内存:
ptr的内存时指针变量所在的地址,&ptr是指针变量所指向变量的值。
即:这里的ptr指向的是变量var
理所应当的ptr的值是var变量的地址
也即是16FDFF2BC
去看一下ptr在内存里的情况:
在这里插入图片描述
在这里插入图片描述
可以看到这里是高地址存放高位,连起来就是 016FDFF2BC 就是var的地址。
而ptr本身的地址是前面的: 16FDFF2B0
这样就很明显了吧
让我们做一个测试去印证下:
我们知道指针的本质其实是地址,让我们再来创造一个指针add
用来显示ptr指针本身的地址:
在这里插入图片描述

这里对变量做出解释:
首先我们定义的整数变量var 是8
这里使用了ptr指向了var。
ptr指针的值,也就是var所在的内存地址。
随后我们使用了指针add去指向ptr,这里由于ptr本身就是一个指针变量,这里就相当于赋值了,将ptr内的var的地址的值,赋值给了指针变量add
接着使用。ptr_add变量,去指向了指针ptr所在的地址,(使用了&),这样ptr_add的值,就是指针变量ptr的值了。

在这里插入图片描述
与上面debug的结果十分吻合。

3.2 指针的用法

首先我们来看一下接下来的操作,我们向内存去申请一部分空间,这里常用的是用char指针去申请堆内存,因为char是一个字节:
示例如下:

#include<iostream>
#define LOG(x) std::cout<< x <<std::endl;
#include "Log.h"
int main()
{char * buffer = new char[8];//用char类型的指针去使用8字节的空间,并将空间的开始地址返回给buffer指针。memset(buffer, 3, 8);//这里的buffer其实存储的就是区域的起始地址。std::cin.get();
}

在我们运行之后,打个断点,观察其内存空间

在这里插入图片描述
这里查看的是buffer指针的值,也就是所申请的内存空间的开始的地址
在我们没有步入line 7的时候,可以看到这部分区域为:
在这里插入图片描述
很显然没有执行memset,让我们继续步入:
在这里插入图片描述
可以看到内存区域被memset设为了8
也就是8个字节的空间为3;
好,跟3.1讲的一样,我们来试一下双重指针。

#include<iostream>
#define LOG(x) std::cout<< x <<std::endl;
#include "Log.h"
int main()
{char * buffer = new char[8];//用char类型的指针去使用8字节的空间,并将空间的开始地址返回给buffer指针。memset(buffer, 3, 8);//这里的buffer其实存储的就是区域的起始地址。char ** ptr = &buffer;delete[] buffer;std::cin.get();
}

这里我们创建了一个指针ptr去指向buffer指针的地址,因此,ptr内的值,就是buffer指针所在的内存的值。
而buffer指针的内容是所开辟堆空间的起始的地址。
让我们来debug一下
先来看buffer指针的内容和其所在内存:
在这里插入图片描述
这是buffer指针的内容,左边是buffer指针所在的内存
紧接着我们来查看一下ptr指针的内容:
在这里插入图片描述
是的,可以看到ptr指针的内容为016FDFF2B0 存放的就是buffer指针的位置!
很简单哦
这就是指针

最后强调一句:
指针的实质就是地址!!!!
不要想太多

4. 引用

引用说白了,跟指针其实很相似,但是引用仅仅只是引用而已,并没有创造新的变量
变量类型& name

这就是引用
来看一个例子:

#include<iostream>
#define LOG(x) std::cout<< x <<std::endl;
#include "Log.h"
int main()
{int a =5;int& ref = a;LOG(ref);ref =10;LOG(a)
}

在这里插入图片描述
可以看到,我们通过修改引用ref 去修改了a的值。
让我们来搞一些复杂的东西:
在这里插入图片描述
可以看到Increment函数的作用是让value自增。
那么结果是多少呢?
6?
不,结果仍然是5
为何呢?
Increment函数实际上是新建了变量a,然后使其自增。
类似于:

void Increment(int a )
{   int a;a ++;value ++;
}

因此这里并没有让a自增。
那么我们应该怎么办呢?
唯一可以做的就是:指针
利用指针的逆写,将修改存入到内存中:
修改如下:
···
#include
#define LOG(x) std::cout<< x <<std::endl;
#include “Log.h”
void Increment(int* value )
{
(value)++;
}
int main()
{
int a =5;
int
ptr =&a;
Increment(ptr);
LOG(a);
}
···
这里的括号是因为,在指针变量递增之前做引用,也就是先逆向引用到指针所存储内存的内容,随后自增。
当然,这一小节的标题是引用:
回想一下为什么直接传入a不行呢?其实就相当于创造了一个新的对象。
那么我们直接传入a的引用就好了

#include<iostream>
#define LOG(x) std::cout<< x <<std::endl;
#include "Log.h"
void Increment(int& value )
{value++;
}
int main()
{int a =5;Increment(a);LOG(a);
}

这里就相当于

int& value  =a ;
value++;

引用做的,指针都可以做。指针的功能室比引用强的。
注意:更改引用是可以的!
如:

int main()
{int a =5;int b =10;int& ref=a;ref=3;LOG(ref)ref=b;LOG(a);LOG(ref);
}

这样是可以的,
但是在声明时,必须立马指出引用变量是引用的谁:
如果不声明,像这样在这里插入图片描述
就会报错!
这就是引用。

5 类

5.1什么是类

我们终于到了面向对象编程了。
如果你想做一款游戏,那么游戏玩家该如何去设计呢?
玩家有坐标、速度等
我们如果不使用一种将变量聚合等技术去创造玩家,
代码belike:

    int Player0X,Player0Y;int Player0Speed;int Player1X,Player1Y;int Player1Speed;

难以维护。。
因此我们使用类
使用calss
在这里插入图片描述
当我创建完毕Player后,尝试去改变其内部的属性值,报错了,被修改的属性是私有的。
这是因为我们没有注意可见性。默认情况下,一个类中的变量都是私有的,只有类内的函数可以去访问这些变量。如果我们希望可以在main中去访问,那么应该加入public
如果我们想要Player去移动,应该在class内编写函数,这种在类内部的函数叫做方法:

在这里插入图片描述

5.2 类和结构体的区别:

类默认是私有的,但是结构体不是
二者的区别在于可见度上
结构体默认为public
由于C中没有类,C++为了兼容C,保留了结构体
所以写了一个define
```#define struct class ``这样就兼容了C中的struct
个人习惯在有大量变量但是没有方法的时候使用struct
反之则使用class
其次就是继承,我只希望我的结构体,是表示一些数据。

5.3 如何去写?

来写一个LOG类
去打印一些调试方便的信息。

#include<iostream>
class Log{
private:int m_LogLevel;
public:const int LogLevelError =0 ;const int LogLevelwarning =1;const int LogLevelInfo =2;
public:void SetLevel(int level){m_LogLevel =level;
}void Warn(const char * message){   if(m_LogLevel>=LogLevelwarning)std::cout<< "[Waring]"<<message<<std::endl;}void Error(const char* message){if(m_LogLevel>=LogLevelError)std::cout<<"[Error]"<<message<<std::endl;}void Info(const char * message){   if(m_LogLevel>=LogLevelInfo)std::cout<<"[Info]"<<message<<std::endl;}};
int main()
{Log log ;log.SetLevel(log.LogLevelwarning);log.Warn("Hello");log.Info("Hello123");log.SetLevel(log.LogLevelError);log.Info("123");
}

这个类分了不同的级别,去打印日志,将级别与数字对应,级别越高,数字越小。当然这是一个非常基本的class示例。

6. 静态

6.1 结构体与类外的静态

这里讲解的是结构体外的静态,我们先来看一下:

#include <iostream>
#define LOG std::cout<<x<<std::endl;
int s_var=10;
int main()
{std::cout<<s_var<<std::endl;
}

这是在main里
我在另一个文件内这样写:
static int s_var =5;
显然这里是以main中声明的为准。
这是因为static声明的变量在link的时候只会从其文件内部进行link。因此这里不会将static的值传入到main里。
如果去掉这里的static
那么只需要在main中的s_var前加入external即可
这被称为external linkage
意思就是在link的时候去文件外找符号。
被static声明后的变量将不会被编译器找到(全局下)
当然对于函数也是一样的。

6.2 结构体中的静态

static其实就是相当于隐形药水的作用。
这里我们引入一个类:

#include <iostream>
#define LOG std::cout<<x<<std::endl;
struct Entity
{int x,y;void Print(){std::cout<<x<<","<<y<<std::endl;}
};
int main()
{Entity e;Entity e1;e.x=2;e.y=3;e1={5,6};e.Print();e1.Print();
}

这样当我们没有为里面的变量设置static时,是可以正常运行的。
但是当我们在x,y加入static后
在这里插入图片描述
这里就找不到x,y了
这是因为x,y被私有了,也就是静态化了,这个变量只有一个实例,我将其称之为static实例,不属于类的范畴了。
当我们在Entity的命名空间内加入x与y时便可以运行。
在这里插入图片描述

在这里插入图片描述
但是结果上来看 e和e1的结果是一样的,是因为x与y被static了,这两个变量只有一个实例,而这里就像是创造了两个变量,但是不属于类,因此这里的e与e1实际上指向的是同一个内存空间。
等同于:
Entity::x=xxx;
Entity::y=xxx;
说白了就是加上 static后 ,属性被私藏了。同样的道理,将方法前面加入static也是一样的调用方法:
Entity::Print()
但是当我们将x与y变为非static
Print仍为static时。。
在这里插入图片描述
静态方法无法访问非静态变量。
可以这样来理解,在方法加入静态后,此方法不属于这个类了,也就自然的不能访问类里面的变量了。方法只属于static类,这也就自然印证了每个非静态的方法总是获得当前类的一个实例作为参数,但是静态的没有实例呀,所以就无法获得参数,自然也就无法运行。要想使其运行,其实有一个方法:给他传入一个实例就好了:
这里我将在结构体外部编写一个Print
在这里插入图片描述
这样就可以成功打印了

总结一句:static的作用是将结构体内的方法/变量 变为只有一个实例,而这个实例只可以在类似命名空间的条件下使用,不属于类的范畴了。也可以理解为加入static后与该类没有关系了。非静态的变量不可以被静态的方法访问,因为静态的方法访问的本质是使用类内的变量进行传参,但是其不属于类,就无法传参。

相关文章:

  • gitlab实现CI/CD自动化部署
  • Kafka 的 ISR 机制
  • 并查集..
  • 智启万象|挖掘广告变现潜力,保障支付安全便捷
  • 集成高精度16bit模数转换ADC电路的两通道测量高精度电容调理芯片 - MDC02
  • C盘磁盘空间不足:VirtualBox的锅
  • 代码随想录 day 39 动态规划 打家劫舍
  • Adobe PhotoShop - 制图操作
  • 【计算机网络——分组延时,丢失,吞吐量】
  • 2024做一个网站要多少钱?
  • 【面试宝典】java多线程面试题总结(中)
  • 学习笔记第二十四天
  • 2024牛客暑期多校训练营7
  • 在IntelliJ IDEA中利用Git拉取项目
  • Midjourney技巧-生成拟人化动物(做你的品牌形象代言人)
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Linux CTF 逆向入门
  • Python打包系统简单入门
  • SegmentFault 2015 Top Rank
  • SSH 免密登录
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 服务器之间,相同帐号,实现免密钥登录
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 和 || 运算
  • 记录:CentOS7.2配置LNMP环境记录
  • 聚簇索引和非聚簇索引
  • 驱动程序原理
  • 如何编写一个可升级的智能合约
  • 如何选择开源的机器学习框架?
  • 算法-图和图算法
  • 通过几道题目学习二叉搜索树
  • 异常机制详解
  • 06-01 点餐小程序前台界面搭建
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (八)Flink Join 连接
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (全注解开发)学习Spring-MVC的第三天
  • (转)IOS中获取各种文件的目录路径的方法
  • (自适应手机端)响应式服装服饰外贸企业网站模板
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • .NET Framework、.NET Core 、 .NET 5、.NET 6和.NET 7 和.NET8 简介及区别
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .Net 知识杂记
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • @EnableConfigurationProperties注解使用
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname
  • [3D基础]理解计算机3D图形学中的坐标系变换
  • [ACM独立出版] 2024年虚拟现实、图像和信号处理国际学术会议(VRISP 2024,8月2日-4)
  • [C puzzle book] types
  • [C++]——继承 深继承