C++是如何工作的
1.预处理
#include :寻找一个指定的文件,将该文件的所有内容拷贝到当前文件中,插入位置即为#include的位置
2.编译
目标平台:x86即windows 32平台,以此类推
解决方案配置:debug:没有优化,好处是可以方便调试代码;release:最大程度地优化
单个文件编译:ctrl+F7
error list:就是个垃圾,会省略很多信息,只能了解一个大概
output窗口:能够提供完整的报错信息
3.链接
链接作用:把所有地obj文件合并成一个exe文件
链接器工作:解析它必须链接的函数符号
4.函数声明(注意与函数定义的区别)
在当前文件使用在其他文件中定义的函数,会引发编译报错,此时必须声明这个函数,编译器会无条件信任在某处确实有这么一个函数。函数声明默认自带extern关键字。
如:void Log(const char* message);
C++编译器
作用:代码文本文件(.c) -> 目标文件(.obj)
报错:未找到函数定义
编译单元(Translation Unit)
在C++中,没有文件的概念(文件没有任何意义)。文件只是为编译器提供源码的一种方式。你只需要告诉编译器这是什么类型的文件以及编译器如何看待它就行了。若创建的文件的扩展名是.cpp,则编译器就会将其视为一个C++文件,类似的,如果创建一个扩展名为.c或.h,那么编译器就会将其视为一个C文件或头文件。
以上均为默认规定,你也可以修改这些规定。
预处理
#include:直接将包含的文件拷贝到当前位置。
#define:直接替换单词。如#define INTEGER int的作用是,搜索所有INTEGER字符并使用int替换它们。
#if:根据if的条件来使得一段代码有效/无效。如:
使有效:
#if 1
int Multiply(int a, int b){
int result = a * b;
return result;
}
#endif
使无效:
#if 0
int Multiply(int a, int b){
int result = a * b;
return result;
}
#endif
正式编译
.obj文件内容是一串二进制代码。
带编译优化和不带编译优化的代码量差别很大。
C++链接器
目的:找到每个符号和函数的位置并将它们链接在一起。
注意:链接既可发生在不同文件之间,也可发生在同一个C++文件中,因为主函数也需要调用其他函数(若该函数已声明但未定义,则链接报错;若该函数未声明也未定义,则编译报错),链接器需要找到主函数的位置。
编译和链接是两个不同的阶段,==若只编译,可以没有主函数==。
报错:未解析的外部符号
如:若Log函数未定义,则下面代码会链接报错
//下面代码会链接报错
#include <iostream>
void Log(const char* message);
int Multiply(int a, int b)
{
Log("Multiply");
return a * b;
}
int main()
{
std::cout << Multiply(5, 8) << std::endl;
std::cin.get();
}
若将Log的调用注释,则不会报错(因为从不调用Log函数,所以链接器不必链接)
//下面代码不会报错
#include <iostream>
void Log(const char* message);
int Multiply(int a, int b)
{
//Log("Multiply");
return a * b;
}
int main()
{
std::cout << Multiply(5, 8) << std::endl;
std::cin.get();
}
若将调用Multiply函数的地方注释掉,则依然会报错(因为虽然当前文件没有用到Multiply,可能其他文件会用到,所以链接器需要链接它)
//下面代码会链接报错
#include <iostream>
void Log(const char* message);
int Multiply(int a, int b)
{
Log("Multiply");
return a * b;
}
int main()
{
//std::cout << Multiply(5, 8) << std::endl;
std::cin.get();
}
若能告诉编译器Multiply函数只在当前文件使用,则可以不连接该函数。可以使用==static关键字==。
//下面代码不会报错
#include <iostream>
void Log(const char* message);
static int Multiply(int a, int b)
{
Log("Multiply");
return a * b;
}
int main()
{
//std::cout << Multiply(5, 8) << std::endl;
std::cin.get();
}
还有一种情况,就是声明函数的返回值类型或者参数类型个数与定义的不一致,也会链接报错。或者是重复定义了该函数,产生了二义性,编译器不知道链接哪个(通常出现在#include文件中出现了函数定义,导致引用的时候重复定义)。
C++头文件
目的:一个只有函数声明而没有函数定义的存储声明的地方。
如果不想每次使用一个函数就声明,那么就可以用#include来自动完成复制和黏贴的操作。
#pragma once 告诉编译器只包含一次
重复包含出错的情况:
void InitLog();
void Log(const char* message);
struct Player{};
两种方法避免重复包含:
#pragma once
void InitLog();
void Log(const char* message);
struct Player{};
#ifndef _LOG_H
#define _LOG_H
void InitLog();
void Log(const char* message);
struct Player{};
#endif
C++调试代码(VISUAL STUDIO)
调试代码的重要两个点:==保存断点==和==查看内存==
必须在debug模式下进行调试,因为release模式会修改代码,可能导致断点永远不命中。
正好在断点的那行代码未执行但即将执行。
==黄色箭头指向的那行代码未执行但即将执行==。
步进:逐行调试,进入当前函数。
步过:执行当前函数,直接到下一行。
步出:执行到当前函数末尾并跳出。
如果想快速跳出多重循环或者复杂的函数,可以在循环外的下面的代码中再设置断点,然后按F5。
C++指针
对指针来说,类型毫无意义,它只是内存的一个地址,一个整数。地址的位数取决于当前运行平台(64位或32位)。
void*指针表示当前不关心这个指针指向的实际的数据类型是什么。
如void* ptr = nullptr;
当需要取内容(*ptr=10)时,就必须要指定类型。
动态分配8个字节内存到堆空间:
char* buffer = new char[8]; memset(buffer, 0, 8); delete[] buffer; std::cin.get();
C++引用
指针和引用的区别:
-
引用必须引用一个已有的变量,而指针可以初始化为空。
-
一旦定义了一个引用,就不能改变。当需要改变时,可以使用指针来实现。
-
引用必须初始化,也就是必须引用一个东西。
int& ref;是语法错误的。
int&可以看作是一个类型。
int a = 5; int* b = &a; int& ref = a;
引用ref并不实际存在,它只存在于源代码中,如果编译这段代码,你不会创建两个变量,而只会得到a一个变量。
引用的常见使用场景:
由于函数的调用只传送副本,调用结束后就会销毁,所以要想实际改变变量的值,必须传递变量地址,并取内容。
void Increment(int* value)
{
(*value)++;
}
int main()
{
int a = 5;
Increment(&a);
}
如果使用引用,就非常方便。
void Increment(int& value)
{
value++;
}
int main()
{
int a = 5;
Increment(a);
}
C++类
类的出现只是为了方便程序员组织和整理代码,让他们看上去更加整洁。类不是必须的,所用需要用类来编写的程序也一定能用不支持类的语言来编写(如C语言)。
C++类和结构体的区别
从技术上讲,它们几乎一样,只是在可见性方面有差别。
技术上的唯一差别:==类默认成员均为私有,而结构体默认成员为公有==。
在编程习惯上:如果只是想表示数据的结构,一般使用结构体。如果想表示一个充满功能的类,就像游戏世界或玩家一样,或者其他可能具有继承性的东西,所有这些系统,一般会使用类。
C++中的Static关键字
在翻译单元的范畴内,static相当于“在类中声明一个私有变量”,顾没有其他翻译单元会看到这个变量,链接器不会在全局范围内看到它。
-
两个不同的翻译单元之间不能有同名的全局变量,否则会链接报错。
例如,一个文件里有int variable = 5;,另一个文件里有int variable = 10,且均为全局变量,则会链接报错。
-
一种解决方法是,在其中一个文件中改为
extern int variable;表示引用外部的variable变量。即:extern int variable;和int variable = 5; -
还有一种方法是,在其中一个文件中改为
static int variable = 5;表示variable在该翻译单元私有,其他翻译单元不可见。即:static int variable = 5;和int variable = 10; -
函数和变量的情况完全相同
鉴于全局变量的弊端,建议尽可能地使用static将变量限制在翻译单元之内,除非你确实想让他们跨翻译单元链接。
C++静态成员
C++静态成员共享内存,所有实例引用同一块内存区域。
struct Entity
{
int x, y;
void Print()
{
std::cout << x <<", "<<y<<std::endl;
}
};
int main()
{
Entity e;
e.x = 2;
e.y = 3;
Entity e1 = {5, 8};
e.Print();
e1.Print();
}
如果把x,y定义为static,那么初始化程序(Entity e1 = {5, 8};)就会失败。因为x,y不再是类成员。正确代码:
struct Entity
{
static int x, y;
void Print()
{
std::cout << x <<", "<<y<<std::endl;
}
};
int Entity::x;
int Entity::y;
int main()
{
Entity e;
e.x = 2; //与Entity::x = 2等价
e.y = 3; //与Entity::y = 3等价
Entity e1;
e1.x = 5; //与Entity::x = 5等价
e1.y = 8; //与Entity::y = 8等价
e.Print();
e1.Print();
}
打印结果是两个5,8。
静态方法:
静态方法不能访问非静态成员。因为静态方法没有类实例(作为参数)。
类背后的工作原理:
==在类中编写的每一个非静态方法总是会获得一个当前类的一个实例作为参数==。
实际上不存在类,他们只是带有某种隐藏参数的函数
struct Entity
{
static int x, y;
static void Print()
{
std::cout << x <<", "<<y<<std::endl;
}
};
int Entity::x;
int Entity::y;
int main()
{
Entity e;
e.x = 2; //与Entity::x = 2等价
e.y = 3; //与Entity::y = 3等价
Entity e1;
e1.x = 5; //与Entity::x = 5等价
e1.y = 8; //与Entity::y = 8等价
e.Print(); //与Entity::Print()等价
e1.Print();
}
非静态方法等价于:
void Print(Entity e)
{
std::cout << e.x <<", "<<e.y<<std::endl;
}
C++枚举类
本质:一种命名值的方式,当你想要使用==整数==来表示==某些状态或某些值==,但是你想给他们一个名称,以便你的代码更具有可读性。
先看下面代码:
int A = 0;
int B = 1;
int C = 2;
int main()
{
int value = B;
if(value == B){
// Do something here
}
}
这段代码的问题是,ABC并没有分组,他

最低0.47元/天 解锁文章
1661

被折叠的 条评论
为什么被折叠?



