学习C++第二部分 the Cherno

学习cpp 第二部分

如何在vs studio中调试

  • 重要的两个内容:断点(break point)、查看内存(reading memory)

  • 断点:程序调试中 中断的点, break意味着暂停,在任何代码行中设置断点,当程序运行至此处时将暂停。暂停后可以查看state,也就是查看内存看发生了什么以诊断问题(debug)。程序运行所需要的内存是很大的,包括要查看的变量、调用的函数等。暂停仍然可以查看内存数据。

  • 通过打断点可以实现程序逐行运行,在vs中,按F9或者侧边框实现。注意:要在合适的地方打断点,在会被执行的代码上打。另外,要在debug模式下断点,在release模式下,编译器实际上会改变代码。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ssUxRor-1683883762512)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230419155210711.png)]

点击▶运行,断点上有一个黄色的箭头,右侧出现诊断工具,点击▶可以继续,上面有三个蓝色箭头:逐语句(step into)、逐过程(step over)、跳出(step out)。step into: 进入到该语句,该语句是一个函数Log,则进入到该函数。step over:跳过这条语句,继续下一行代码。step out:跳出该函数,回到调用函数的地方。该例中回到main函数,意味着回到c++标准库中。

  • 点击step into:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o293Lwqe-1683883762513)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230419160405522.png)]

页面跳到Log函数的definition处,查看message变量可以看到在本例中为helloworld! 黄色箭头表示代码即将运行到这一行,按下F10(step over)或shift+F11(step out)或F5(继续)就可以运行这一行代码,即打印到console。

  • 修改Main.cpp
#include<iostream>
#include"Log.h"

int main()
{
	int a = 8;
	a++;
	const char* string = "Hello";

	for (int i = 0; i < 5; i++)
	{
		const char c = string[i];
		std::cout << c << std::endl;
	}

	
	Log("helloworld!");
	std::cin.get();
}

断点打在int a = 8;上,可以看见a并没有被创建出来赋值,而是并将其将要设置的内存位置显示出来了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGbPkGUr-1683883762514)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230419171125711.png)]

在该窗口中有autos,locals和watch。前两个可能展示重要的变量和局部变量,watch观察变量,可以输入变量。后面可以显示内存中的值,可以看到当前变量都是未被定义的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TE4DwOis-1683883762515)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230419171327836.png)]

点击调试 >> 窗口 >> 内存,可以看到被定义的几个内存。输入&a查看其内存位置,为十六进制码,两个数字为一个字节,两个十六进制数与一个字节对齐。大量的cc表示未被初始化的栈内存,调试时看到cc意味着没有初始化这个变量。点击F10,发现窗口中a的值变成8,红色表示自最后一个断点。内存窗口也发生了变化。

继续点击F10,运行到a++,窗口中a的值变为9,继续F10,出现变量string,复制窗口中的值到内存1中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGJ6Bn9A-1683883762517)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230502194938831.png)]

这几个ASCII码代表的含义即为Hello

继续,发现仍在for循环中,内存1输入&c,变量c依次变为Hello。如果要跳出该for循环,可以在for函数之外,Log处打上断点,点击F5或者继续,此时c变量的内存仍然活跃,c值为o。点击F11将跳出整个代码。继续,控制台出现Hello World!,但点击回车无反应,点击继续,控制台关闭。

总结:程序是由内存组成的,无论是指令或者指针,都是代码,都储存在内存中,所以看到内存是非常重要的。通过设置断点,暂停程序,在给定的时间在给定的代码行,查看变量是什么,对代码非常有用。

C++条件与分支(if语句)

  • 写代码的时候有时需要一个特定的条件进行评估,根据评估的结果,决定想要执行什么代码。

  • 在条件的基础上可以进行分支。此时有两种情况:当运行到if语句时,先是对实际条件语句评估,然后是基于这个条件语句评估后的分支语句,也即是当条件为真时,程序跳到一部分代码中;条件为假时跳到另一部分(源代码)。在运行的应用程序中,指机器指令。

  • 开始一个应用程序时,整个程序机器所有模块加载到内存中,也可以说是所有的指令组成了应用程序。

​ 流程:检查条件- > 跳转到内存的不同地方- > 执行指令。条件语句开销大,运行慢,优化代码尽量避免使用条件语句。

  • Main.cpp:
#include<iostream>
#include"Log.h"

int main()
{
	int x = 5;
	bool comparisonResult = x == 5;  //存储bool值,即比较的结果。要么结果为真,要么为假。
    if (comparisonResult) //省略:comparisonResult = 1
    {
        Log("helloworld!");
    }
	std::cin.get();
}

==符号是在C++标准库杯重载了,相当于写一个函数,接受两个整数参数,然后检查这两个整数的内存以确保两个整数相等。即获取4个字节的内存并比较内存的每一位。

  • 在if行打上断点并运行,可以看到comparisonResult的值为true;若将x的值赋6,则comparisonResult的值为false,并且断点跳过if函数的主体部分,控制台无输出。

  • 详细查看CPU运行了一些什么指令,可以通过编译并查看汇编语言。在断点代码行处右键转到反汇编

	int x = 5;
00007FF6DBDA194B  mov         dword ptr [x],5  //将5移动到寄存器
	bool comparisionResult = x == 6; 
00007FF6DBDA1952  cmp         dword ptr [x],6  //将6移动到同一个寄存器
00007FF6DBDA1956  jne         main+34h (07FF6DBDA1964h)  //jump not equal,如果两个值不相等,跳到该内存地址中
00007FF6DBDA1958  mov         dword ptr [rbp+0F4h],1  
00007FF6DBDA1962  jmp         main+3Eh (07FF6DBDA196Eh)  
00007FF6DBDA1964  mov         dword ptr [rbp+0F4h],0  //跳到该行,0是一个偏移量,0加载到寄存器中,赋给bool值。对于bool值来说,0为false,1为true。但只要布尔值一个字节中由任何一位不为0,都是true。
00007FF6DBDA196E  movzx       eax,byte ptr [rbp+0F4h]  
00007FF6DBDA1975  mov         byte ptr [comparisionResult],al  

	if (comparisionResult)
00007FF6DBDA1978  movzx       eax,byte ptr [comparisionResult]  
00007FF6DBDA197C  test        eax,eax  //将一些值加载到eax寄存器中,测试寄存器是否通过条件。然后对寄存器进行一些逻辑运算。
00007FF6DBDA197E  je          main+5Ch (07FF6DBDA198Ch)  //条件跳转语句,jump equal 如果两个值相等将跳到某位寄存器中。

确保在debug模式下运行。

#include<iostream>
#include"Log.h"

int main()
{
	int x = 5;
	bool comparisonResult = x == 5;  
    if (1) //如果条件为真,则运行if语句
    {
        Log("helloworld!");
    }
	std::cin.get();
} //最终打印helloworld
  • 简化:
#include<iostream>
#include"Log.h"

int main()
{
	int x = 5;
    if (x==6) 
        Log("helloworld!");//只有一行代码无需花括号。并且分行,否则断点无法分析CPU运行了什么。
	std::cin.get();
}
  • if语句只对数值进行检查
#include<iostream>
#include"Log.h"

int main()
{
	int x = 5;
    if (x) //x不为0则语句为真
        Log("helloworld!");
	std::cin.get();
}
  • 还可以检查指针是否为空(null为0)
#include<iostream>
#include"Log.h"

int main()
{
	const char* ptr = "Hello";
    if (ptr) 
        Log(ptr);
    
	std::cin.get();
} //打印Hello
#include<iostream>
#include"Log.h"

int main()
{
	const char* ptr = nullptr;
    if (ptr) //相当于if(ptr!=nullptr)
        Log(ptr);
    
	std::cin.get();
}//无输出
  • 加上else
#include<iostream>
#include"Log.h"

int main()
{
	const char* ptr = nullptr;
    if (ptr) 
        Log(ptr);
    else
		Log("ptr is null!");
	std::cin.get();
}//输出语句ptr is nunll
  • 加上else if
#include<iostream>
#include"Log.h"

int main()
{
	const char* ptr = "Hello";
    if (ptr) 
        Log(ptr);
    else if(ptr == "Hello")
        Log("ptr is Hello");
    else
		Log("ptr is null!");
	std::cin.get();
}//输出Hello

else if语句:只有在if语句失败时才执行,并且相当于else{}

#include<iostream>
#include"Log.h"

int main()
{
	const char* ptr = "Hello";
    if (ptr) 
        Log(ptr);
    else 
    {
        if(ptr == "Hello")
        Log("ptr is Hello");
    }
    else
		Log("ptr is null!");
	std::cin.get();
}
  • 变成可以分成两类:数学编程和逻辑编程。

Visual Studio的最佳设置

  • 创建新项目,解决方案自动设置为和项目一样的名称,但在实际情况中会避免这一现象。

  • 创建新项目后,在侧边工具栏中会出现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iC7NVr5F-1683883762518)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230506134332825.png)]

下面一些文件实际上是过滤器,右键项目打开所在文件夹是没有的。

  • 在源文件中创建Main.cpp,点击上方显示所有文件,发现这些过滤器没有了,只有

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZV7W2ppx-1683883762519)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230506134609510.png)]

实际上是硬盘里的目录结构。而文件的目录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmVrVHye-1683883762520)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230506134742393.png)]

比较乱,这时候可以在VS中创建 一个src文件夹用来专门存放源文件,将Main.cpp移动至src文件夹。如图,同时硬盘中的目录结构随之变化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JD3GAJpF-1683883762521)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230506134922789.png)]

再次点击显示所有文件按钮,过滤器显示出来,这意味着可以将Main.cpp移动到任何过滤器中而目录结构不变。

  • Main.cpp F5编译
#include <iostream>

int main()
{
	std::cout << "hello" << std::endl;
}
  • 查看与src文件同级的x64文件,内有debug文件,打开却只有中间文件,没有exe文件,实际上在src上一级文件的x64 >> debug中

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlOGxNIJ-1683883762522)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230506135824334.png)]

  • 右键project1>>属性设置中间目录和输出目录,分别是$(SolutionDir)bin\intermediates\$(Platform)\$(configuration)\

$(SolutionDir)bin\$(Platform)\$(configuration)\

保存后,右键清理,在文件资源管理器中删除无关文件夹,包括刚开始的中间文件等,再次编译,发现中间文件和输出文件都在bin文件夹中。由此实现最佳配置。

  • 如果想知道宏是什么,可以在属性>>目录>>编辑中查看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtpUI8If-1683883762524)(C:\Users\陈周念\AppData\Roaming\Typora\typora-user-images\image-20230506144155979.png)]

C++循环 (for、while)

  • 使同样的工作执行多次

  • 可以选择在代码中将函数申明5次,但这样并不方便

  • 使用for循环语句:

格式:for ( ; ; ),括号内由三段组成,第一段,申明变量i的类型以及大小;第二段;语句只要为真,那么将一直执行for循环语句;第三段将在for循环的下一次迭代之前调用

Main.cpp

#include <iostream>
#include "Log.h"

int main()
{
    Log("Hello World");
    Log("Hello World");
    Log("Hello World");
    Log("Hello World");
    Log("Hello World");
    std::cin.get();
}

改为

#include<iostream>
#include "Log.h"

int main()
{
    for(int i = 0; i < 5 ;i++)
    {
        Log("Hello World");
    }
    std::cin.get();
}

等价于:

#include<iostream>
#include "Log.h"

int main()
{
    int i = 0;
    for( ; i < 5 ; )
    {
        Log("Hello World");
        i++;
    }
    std::cin.get();
} //两段声明可以空着,第一段将i在for循环之外首先声明,第三段在for循环内声明

或者将第二段改为:

#include<iostream>
#include "Log.h"

int main()
{
    int i = 0;
    bool condition = true;
    for( ; i < 5 ; )
    {
        Log("Hello World");
        i++;
        if (!(i<5))
        {
            condition = false;
        }
    }
    std::cin.get();
} //没有改变代码的实际行为,第二段实际上是布尔类型。或者第二段只有空格,表示true,语句永远为真,无线打印HelloWorld
  • while语句

while( );与for不同,仅有括号内条件判别语句

#include<iostream>
#include "Log.h"

int main()
{
    int i;
    for(i = 0; i < 5 ;i++) #在for语句中申明的i为局部变量
    {
        Log("Hello World");
    }
    Log("==================");
    i = 0;
    while (i < 5)
    {
        Log("Hello World");
        i++;
    }
    std::cin.get();
}
  • 两者区别

实质上没有区别,看个人喜好。但例如在游戏中存在一个变量running,指让游戏一直运行下去,这时候选择while更好,因为条件并不会改变,不需要在循环前先声明这个条件变量,可以将之前的变量或者函数调用的结果直接拿来用,不需要更新或者初始化某些东西。

使用for的情况:比如有数组的情况,处理10个数组,同时要跟踪某些变量,并且这些变量可以用于处理数组里面的元素,有时用offset偏移量或者索引处理元素。

  • 不常用的语句dowhile

do{ }while(条件);与while的区别是不论如何人需要先执行花括号内的循环体一次。

c++控制流语句

  • 控制流语句通常和循环语句一起使用,控制循环语句的实际执行,一般有三种,continue、break、return
  • continue:只能在循环语句中使用,表示进入循环的下一个迭代,如果没有下一个迭代则结束循环。
  • break:用在循环中和switch语句中,表示跳出循环,结束循环。
  • return:用在函数中,可以直接退出函数,用法非常广泛。函数需要返回值,如果只有return,返回return本身,return本身只适用在void函数中,函数需要返回时需要提供一个值。

continue:

#include<iostream>
#include"Log.h"

int main()
{
    for (int i = 0; i < 5; i++)
    {
        if (i % 2 == 0)
            continue;
        Log("Hello World");
        std::cout << i << std::endl;
    }
    std::cin.get();
}

表示i为偶数时直接跳进下一个迭代,执行for语句中的i++,并评估语句中的条件,i为奇数时不跳,直到循环结束。输出:

Hello World
1
Hello World
3

将if语句中的条件改为i>2, 可以类比。输出

Hello World
0
Hello World
1
Hello World
2
  • break: 将continue换成break,
#include<iostream>
#include"Log.h"

int main()
{
    for (int i = 0; i < 5; i++)
    {
        if (i > 2 == 0)
            continue;
        Log("Hello World");
        std::cout << i << std::endl;
    }
    std::cin.get();
}
  • 输出仍为:
Hello World
0
Hello World
1
Hello World
2

条件换成i%20,没有任何打印。条件换成(i+1)%20, 打印:

Hello World
0

表示程序一旦到达break语句,循环结束,到下一行语句。

  • return:对于举例中的int main()函数来说,return需要返回一个整数。
#include<iostream>
#include"Log.h"

int main()
{
    for (int i = 0; i < 5; i++)
    {
        if ((i+1) % 2 == 0)
            return 0;
        Log("Hello World");
        std::cout << i << std::endl;
    }
    std::cin.get();
}

控制台闪退,因为并没有运行到std::cin.get();

通过打断点可以看到程序有输出

Hello World
0

并且在第二次循环中到达return 0 ;时直接跳到最后的花括号。

#include<iostream>
#include"Log.h"

int main()
{
    for (int i = 0; i < 5; i++)
    {
        if ((i+1) % 2 == 0)
            return 0;
        Log("Hello World");
        std::cout << i << std::endl;
    }
    
    return 0;
    std::cin.get();
}

std::cin.get();这一行永远不会到达,实际上毫无用处,称为死代码。

  • 条件语句、循环语句和控制流语句、是唯一可以改变程序行为的东西,不要尝试写入指令指针。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值