The Cherno C++ 系列教程笔记(上)

本篇笔记将记录 Cherno 的 C++ 系列的所有令初学 C++ 者眼前一亮的知识点,而简单的语法知识和基本操作在此不做笔记,强烈建议新手完整地观看全系列教程。数组词

注意,每 P 的知识点不是孤立的,可能会在后面更加ios15.4正式版推送时间深入地、全面地拓展讲解,有些简单的知识点可能会更多地讲底层和优化,因此都是值得认真学习和细细琢磨的。

P5. C++ 是如何工作的

# 符号后都是预处理语句,他们都会在编译之前执行,如如下代码中的 include 语句,它会寻找一个叫 iostream 的文件,找到后会将 io静态成员变量stream 文件内的所有内容完整地拷贝到当前文件中。此外,这种文件又被称为头文件 (head f指针万用表怎么读数ile)。

#include <iostram>
#define
#ifndef
#endif

P6. C++ 编译器是如何工作的

在 C++ 中任何符号都需要声明,我指针式万用表们对每个文件都会单独编译,如果指针万用表的使用方法调用一个外部函数,却不在当前文件进行声明,那么当前文件就不知道还有这样的一个函数存在。如果你声明了却没有定义函数,编译器依旧完全相信你,但链接器试图寻找你定义的函数时却ios模拟器找不到该函数就会报链接错误数组c语言。如果都正确了,编译器会将每个文件但单独编译为 .obj 文件,而链接器会将他们合并为一个可执行文件,例如 exe 文件。

编译器将文本转换成一种称为目标文件的中间格数组去重方法式,在这个ios下载过程中,编译器成员变量有默认值吗会先预处理代码,将我们的代码转为抽象的语数组c语言法树,然后生成 CPU 所执行的代码.

P7. C++ 链接器是如何工作的

C++ 程序必须有一个入口函数,指针说漫main 函数,你也可以在 IDE 中修改这个函数名。但链指针接器总会寻找你所指定的入口函数,如果没有定义它,就会报链接器错误。

由于编译器是相信你的,但假如你在文件中声明了函数,却没有真正地定义,此时只有链接器才会找到这个错误。即使你ios系统并没有调用这个未定义地函数,链接器依旧会报错,因为链接器不能确定别的文指针数组和数组指针的区别件就不会调用。但如果用 static 关键字修饰函数,那么该函数就只在当前文件生效,也就是说链接器就不会认为由外部成员变量和静态变量的区别文件调用该函数。

链接错误

如果在同一的文件存在相同的索引的作用定义,那么编译器就会检查到该错误,但如果是不同的文件存在相同的定义,只能由链接器检查指针和引用的区别到该错误。当使用 #include 语句时可能会导致头文件多次被拷贝在不同文件中,也就会触发这种链接错误。我们可以索引页是哪一页通过将声明都放在一个头文件中,不同的翻译单元分别包含头文件,避免定义被拷贝。

P.8 C++ 中的变量指针数学

变量的类型大小和它能存储多大的数字时直接相关成员变量的。一个整型是 4 字节,也就是 32 位的数据,如果这个类型有符号,那么需要其中 1 位表示正负,也就是说该类型实际能表示 2312^{31} 个可能的值,那么整型变量的取值范围就是 2成员变量和局部变量312^{3数组长度1}。如果将符号去掉,就是无符号类型 unsig指针ned ,其取值范围增加,但只能是大于等于 0 的值。值得注意的是,bool 类型的值非 01,理论上只需要 1bit 的指针说漫内存空间,但是在内存中寻址无法寻找只有 1bit 的指针内存,因此,声明一个iOS bool 类型变量时,依旧会占用 1 个字节的内存。注意,一个指针万用表的使用方法类型到底有多大,这取决于编译器,索引的作用但我们可以使用 sizeof 操作符查询类型大小。

sizeof(int);

P索引的优缺点9. C++ 中的函数

函数的目的是减少重复,不要为频繁地创数组建函数,因为每次调用函数,指针编译器都会生成一个 c索引符号alios模拟器l数组的定义令。编译器需要为每个函数创建一个栈结构,然后把返回地址和函数参数等压入栈。然后运行时就会跳转索引的优缺点到二进制执行文件的不同部分,以执行我数组词们的函数索引是什么意思命令。

main 函数没有返回值这是现代索引是什么意思 C++ 的一个特性,编译器会为你在末尾处成员变量和静态变量的区别添加一行 return 0; 这只是为了让你的代码更ios越狱加干净。但别的函数如果没有返回值,在 Rios15.4正式版推送时间elease 模式下是不会报错的,如果真的调用这个函数数组词,依旧会报 “未定义的行为” 的错误,在 Debug 模式下,就会直接提示你没有返回值。

P10. 头文件

C++ 中头文件通常用于声明成员变量和局部变量某些类型的函数,以便于程序中的ios14.4.1更新了什么调用。而预处理语句 #pragma once 监督当前头文件只被包含一次。例如该命令会检查 _LOG_H 是否被定义,如果被定义指针电影了,其包括的代码都不会包含。

相比较 #ifndef#endif 的形式,实际上二者没什么区别,只是 #pragma once 预处理语句更加简洁。

#ifndef _LOG_H
#define _LOG_H
//...
#endif

头文件的包含符号

<> 符号引入的头文件,会搜索该绝对路径下的头数组指针文件,'' 符号引入成员变量和局部变量区别的头文件,会包含相对于当前文件的头文件。而 Chern索引o 本成员变量和静态变量的区别人的风格是要包含自己写的头文件是,一律使用 ''。此外,类似 iostream 的这中头文件没有文件扩展名指针数组和数组指针的区别,这是因为 C++ 标准库为了和 C 标数组指针准库进行区分而规定的,但这不影响编译索引页是哪一页器能识ios14.4.1更新了什么别出它是一个头文件。

P12. C++ 中的条件语句和索引页是哪一页分支

if 条件语句只是在处理数字,是 0 就是 false,不是 0 就是 true。当我们创造了一个布尔值,实际上索引下推会占用 1 字节的空间,不一定要知道该字节中哪个 bit 位被设为了 1,只要有 1 个不是 0 ,那么这个 boios15.4正式版ol 类型的 1 字节内指针存就代表了真。其底层过程就是 MOVE 指令将 0 加载到内存中寄存器中,它等于 false,于是将该布尔值设为 falseIF 指令某些值加载到 EAX 寄存器中。如果编译器自己就能确定比较结果,不需要再运行时在做比较,这种情况被称为数组去重方法常数折叠,优化会自动去掉与之相静态成员变量关的 bool 值、if 条件语句等等,直接索引的优缺点跳过。

int x = 6;
bool result = x==5;

else if指针数组和数组指针的区别不是一个成员变量有默认值吗关键字,而是一索引的优缺点个语法糖。

else if(){
}
//equals
else{
    if(){
    }
}

P16. C++ 中的指针

指针是一个索引的优缺点整数,一种存储内存地址的数字,把内存想象数组指针成一条直线,一排房子,每个房子是一个字节能住 8bit 的 01,每个房子都有一个地址。我们需要一种方式来寻址任意的字节。而类型只是让我们为了让生活更容易而创造的虚构的概念,任何类型的指针,它都是ios是什么意思一个保存着内存地址的整数。此外,0 不是一个有效的内存地址,给指针赋值 0 意味着这是一个无效的指针,事实上 NULL 常量就是 0,我们ios15.4正式版推送时间也可以赋值为 nullptr

P17. C++ 中的引用

引用是指针的伪装,只是在指针之上的语法糖,使指针更易阅读和理解。我们可以将一个指针的值设为 0 无效指针),但不能对一个引用这样做,因为引用必须要能 “引用” 一个变量,必须是一个已经存在的变索引是什么意思量。引用本身并不占用内存,没有自己的存储空间,编译器也不会新建一个变量,会直接用原变量替代引用,换句话说引用指针c语言只是变量的别名。

fuction(&var);
void fuction(int* value){
    (*value)++;
}
//equals
function(var);
void fuction(int& value){
    value++;
}

P.19 C++ 中的类 VS 结构体

classstruct 二者的唯一区别就是 class 默认成员为私有, struct 默认成员为公开。二者之数组长度所以这么相似而不统一,是为了兼容 C 语言,才保留了 stios模拟器ruct 关键字。

P.21 C++ 中的静态

C++ 中的 static 关键字的意思取决于上指针说漫下文,在类或者结构体外部使用 static 关键字和内部使用是不一样的。在类外使用 static 这意味着该成员只在内部生效,意味着其作用域仅限于该 cpp 文件内,意味着只索引是什么意思对该编译单元可见,链接器不会再该编译单元外寻找它的定义。

如在两个独立文件中分别定义了相同变索引符号ios15.4正式版推送时间,这显然不会被链接器通过。如果加上 static 关键字修饰其中一个变量,那么该变量就不会被链接器所搜寻,只在自己的文件内生效,有点类似于声明了一个私有变量,别的翻译单元都不能看到这个变量但如果我们将其中一个的赋值删掉,加上 extern 关键字,那么编译器就会在外部的翻译单ios15元中寻找该变量,这也能避免重名变量的冲突指针说漫

P22. 类和结构体的静态

而类或者结构体内的 static 代表着被修饰的成员会被该类型的所有实例共享这块内存。静态的类成员变量或方指针万用表的使用方法法是无法直接访问类的非静态变量或方法的。例如一个类的静态方法,只能通过函数参数传入类实指针数学例的形式进行访问,而不能直接在函数内访问类非静态成员变量或方法。

P23. 局部的静态

声明一个变量时我们需要考虑变量的作用域和生命周期,而数组长度局部的静态变量就是只在作用域中生效,但其生命周期直到程序终止才结束。

当函数内存在静态变量时,函数第一次被调用,该静态变量被初始化,并可供该函数的后续所有指针数组的调用提供同一变量,而不是创建新的变量。后者也能提供相同的作用,但区别就是后者的方式还会导致该静态变量在文件中全局可调用,而前者只在声明它的作用域中生效。

void function(){
    static int i = 0;
    i++;
}
static int i = 0;
void function(){
    i++;
}

P28. C++ 中的虚函数

虚函数引入了动态联编 (dynamic dispat成员变量和局部变量区别chios下载),通过虚函数表来实现编译。虚函数表就是一个表,其包含基类中所有虚函数的映射,以在运行时能正确地覆写 (override) 函数。

数组c语言函数并不是没有开销地,我们需要额外的内数组c语言存来存储虚函数表,以正确地覆写函数组词数;基类中还要有一个成员指针指向虚函数表。当ios越狱我们调用虚函数时,就需要遍历一次虚函数表以确定映射到哪个函数,这些都是额外的性能损失,虽然损失非常小。

P31. C++ 中的数组

C++ 数组就是表示一堆变量组成的集合,通常是一行相同数据类型的内存。当是你通过数组索引访问不存在数组元素时,就会产生内存访问违规 (Memory access violation) ,在 Debug 模式下会提示你,但在 Rel索引失效的几种情况ease 模式不会产生报ios下载错信息。所以使用原生数组要时刻留意边界问题。

在内存中,数组会被连续地分配在一段内存中,指针其大小为以数据类型的的大小乘以数组长度。而数组的变量实际上就是一个指针静态成员变量,指向了数组的第一个元素的地址。因此我们直接将指针的地址+索引是什么意思n,就能移动到数组的其他成员的地址。

int array[5];
int* ptr = array;
array[2] = 5;
//equals
*(ptr + 2)=6;

这里的 +n 并不是增加 n 个字节,实际上会自动乘上类型的字节大小。例如我们可以先将指针地址转为 char* 指针,由于 ch静态成员变量ar 类型只占 1 个字节,因此需要 +8 才能移动到数组的第 3 个元素的地址。然后将已经数组长度指向数组第三元素地址的 char* 指针转换为 int* 指针,最后解引用,对该地址指向的内索引下推存中的数值进行修改。

*(int*)((char*)ptr + 8) = 6;

数组只是一个连续的数据块,我们可以想索引一本书一样索引它们。在堆上创建数组,iOS会导致间接寻址,栈成员变量和局部变量上存储的指针电影数组变量不再直接指向数组,而是数组的内存在堆上的地址,需要进行跳转,显然这会影响性能。

P32. C++ 中的字符串是如何工作的 &ampios系统; 如何使用它们

字符串是不可变的,会被固定分配一块内存块。现在已经指针数组和数组指针的区别不允许在声明ios下载如下形式字符串时,不加上 constiOS 关键字了。字符串在内存中除了自身的字符之外还成员变量和局部变量会再结尾多一个 00 字节,这是终止符。通过终止符我们能告诉编译器字符串有多长,看到终止符字符串就结束了。ios15

const char* name = "Cherno";

在内存ios下载视图中,我们能看存储的数据周围有很多值为 cc 的字节,这些字节被称为数组守卫,在 Debug 模式下分配数组等会插入栈守卫之类的,这样可以判断我们是索引失效否将数据分配在内存之外。此外,我们要注意在函数参数中字符串要尽量ios15.4正式版写成引用形式,因为字符串的复制是有消耗的,避免不必要的性能浪费是ios越狱值得的。

void Print(const std::string& string){
    string += "h";
}

P33. C++ 中的字符串字面量

例如 "Cherno" 就是一个字符串字面量,但其长度是 7 而不是 6,这是因为会自动加一个空终止符 n

"Cherno"
//equals
"Chernon"

当我们使用 strlen 函数输出字符串长度时会发现,遇到终止符后长度就停止了,而不是和声明的字符串数组长度有关。同样的,即使只字符串中没有终止符,其仍索引下推不会记录字符串末尾的终止符为长度,因此长度会比你声明的数组大小少 1。

const char name[8] = "Chenrno";

字符串字面量会存储在二进制文件的 CONST 部分,当我们引用这个字符串字面量时,实际上指向的时一个我们不能编辑的常量区域。但如果使用数组的形式iOS声明字符串,就不会指向常量区域而是一个正常的内存块,那么我们就可以修改该字符串了。用指针的形式声明的字符串常量,对其修改是一个未定义行为,可能在 Release 模式下不会警ios14.4.1更新了什么告你,但运行时不会通过该行为的。

其他类型的字符

char16_t 是两个字节 16bit 的字符,char32索引超出了数组界限什么意思_t 是四个字节 32bit的字符,也就是 utf8utf16wchar_t 是宽字符,一般也是两个字节 16bit,但该类型所占字节实际是静态成员变量多少还是要取决于平台和编译器。

const char* name = u8"Cherno";
const wchar_t* name = L"Cherno";
const char16_t* name = u"Cherno";
const char32_t* name = U"Cherno";

由于字符串字面量本质上是 c成员变量和局部变量区别har* 指针,不能通过对两个指针的直接相加来实现字符串的拼接,我们可以通过将前者用 std::string指针数学型的构造器将字符串字面量传ios是什么意思入的形式,使前者成为一个 s指针c语言td::string 对象。

std::string name = std::string("Cherno") + " hello";

而 C++14 提供了一个新特性使得字符串能够相加,在前者的末尾加上 s 就成为成员变量 std::string 类型的对象,在前面加上 u8L 等前缀就成了其他字符类型的对象。

using namespace std::string_literals;
std::string name = "Cherno"s + " hello";

P34. C++ 中的 const

const 是一个假的关键字,什么也没做,只是ios系统让你作出ios模拟器承诺不会改变。但承诺索引的作用是可以被打破的,是否遵守承诺这取决于你。

const 与 指针的组合

如下代码中,成员变量前两者相同,都不能修改 *a指针c语言而第三者不能修改 a,最后一个是*a,a都不能修ios系统改。我认为就看const*a,a的关系,const*a 前就是 *a (地址指向的值)不能修改,在指针数学 a 前就是指针本身不能修改。而 const int* c成员变量和局部变量区别onst a; 就是 *a,a 都被 const 修饰,都不能修改,因此第四个还能写成这种形式:int const * const a = new int;

const int* a'
int const* a;
int* const a;
const int* const a;

const 在类和函数中的应用

对函数用 const 关键字修饰后,将成员变量有默认值吗不能在函数中修改类的成员变量,如果非要修改需要为该变量添加一个 mutable 关键字的修饰。如果一个 const 的类型实例需要调用类的非静态函数,这是不被允许的,因为你无索引超出了数组界限什么意思法保证该函数没有修改类型的成员变量,而类型中 const指针数学 修饰的函数则可以保证,因此开发中往往会提供一个函数的 const 关键字修饰的版本和一个普通的版本。

class Entity{
private:
    int x;
    mutable int y;
public:
    int GetX(){return x;}
    int GetX() const {
        y = 0;
        return x;
    }
}
void PrintEntity(const Entity& e){
    std::cout<<e.GetX()<<std::endl;
}

const 函数参数下的指针和引用

参数中的 const Entity* e 是一个不能修改指针指向内容但可以修改指针的参数。而参索引失效的几种情况数中的 const Entity& e 就是传入函数的变量本身,只是在函数中使用引用而不是变量就不会复制一份实例,以减少开销。因此在函数中 e 就是实例的内容,可以直接调用。

void PrintEntity(const Entity* e) {
    std::cout << e->GetX() << std::endl;
}
//equals
void PrintEntity(const Entity& e) {
    std::cout << e.GetX() << std::endl;
}

评论

发表回复