超全C++学习笔记,适合小白 第一部分(最好的C++视频 来自the Cherno)

学习cpp 第一部分

How to set up cpp in linux

mkdir ws/src 

cd ws

touch src/Main.cpp #touch:创建新文件

  • 编译CmakeList.txt文件:是cmake用来生成Makefile文件需要的一个描述编译链接的规则文件

vim CmakeList.txt

cmake_minimum_required VERSION(3.0) #指定的cmake最低版本

project(HelloWorld) #为项目命名

set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -std=c++14") #编译选项 显示警告和错误 std标准库版本可选11或14(set指令可以用来显式的定义变量)
set (source_dir "${PROJECT_SOURCE_DIR}/src/") #源码目录 设定source_dir变量指向该路径下的src目录,PROJECT_SOURCE_DIR为当前目录,根目录下有放置代码的src文件

flie (GLOB source_files "${source_dir}/*.cpp") #GLOB:指定源码都在source_dir中,设置文件的类型为cpp的全部编译,

add_executable (HelloWorld ${source_files}) #添加可执行文件HelloWorld 

注: gcc ./source/*.c -o ./bin/test -I ./include -L ./lib/ -l动态库名

上述命令的解释为:用gcc工具编译当前目录下source文件夹中的所有的.c文件 生成目标为test的可执行文件且将其放在当前目录下的bin文件夹中,其所用到的头文件所在路径为当前目录下的include文件夹,动态库文件路径为当前目录下的lib文件夹,编译时需要用到的动态库为库名所对应的.so动态库

  • 运行CMAKE脚本:编译build.sh,不是构建运行程序,而是生成项目文件

vim build.sh

#/!bin/sh #bash正常运行脚本
cmake -G "CodeLite - Unix Makefiles" DCMAKE_BUILD_TYPE=Debug #cmake构建格式:debug,cmake -G:指定生成器,Unix or Ninja
chmod +x build.sh  #添加可执行权限
./build.sh #生成构建文件:cmake。。
codelite HelloWorld.workspace & #&:后台打开轻量级编译器

Main.cpp代码:

#include<iostream>

int main()
{
    std::cout << "HelloWorld!" << std::endl;
    std::cin.get();
}
  • build > build project 编译

可以在终端./HelloWorld运行或者再CodeLite > 首选项 > 构建内置终端

参考资料:https://blog.csdn.net/m0_53685032/article/details/126988487

How cpp works

  • compiler(编译器)将cpp代码编译成为binary(二进制文件),binary:可以是library(库)或者executable program(可执行文件)
#include<iostream> #井号开头的语句都是preprocessor statement(预处理指令),编译器首先预处理这些指令,将相关代码复制粘贴到文件中。include名为iostream的header file(头文件),以包含函数cout、cin的declaration(声明)。cout:打印语句到console(控制台)。
int main() #每一cpp文件都包含main函数,称为程序的entry point(入口点)。程序被按顺序编译,control flow statement(控制流语句)或则其他函数可以改变编译顺序。
{
    std::cout << "HelloWorld!" << std::endl; #endline:告诉console前进一行
    std::cin.get(); #等待回车,然后执行下一行代码
} #main函数返回类型为int整型,函数并没有返回一个integer(整数),是因为main函数是无需返回值的函数类型,若无规定,自动返回0。
  • 在Windows系统下,每个cpp文件会被编译成为扩展名为obj的文件。每一cpp文件被单独编译为obj后,linker将它们联系起来并成为exe文件。obj + obj -linker-> exe(通过查找函数签名)。

  • control+F7调出output(输出),可以显示详细的错误信息;error list(错误列表)调出的信息实际上是从output中解析出来的关键字信息。右键项目>build(生成)可以编译整个项目生成exe文件(debug文件下)。

#include<iostream>

void Log(const char* message)
{
    std::cout << message << std::endl;
}

int main()
{
    Log("Hello World!");
    std::cin.get();
}
  • 分为两个文件以便代码层次更分明
#include<iostream>

void Log(const char* message)
{
    std::cout << message << std::endl;
}
#include<iostream>

void Log(const char* message); #该程序中关于Log函数的declaration

int main()
{
    Log("Hello World!");
    std::cin.get();
} #在该文件中,只要declare了Log函数的存在,即可编译成功,因为compiler无法resolve(解析)log函数在哪里被definition(定义)。definition:含有函数的名字和body(主体),declaration只含有名字。
  • 单独编译一个文件,只要语法正确,有declaration,就可以编译成功;build整个项目可能会报错unsolved external symbol(未被解析的外部标记)。

How the cpp compiler works?

  • 文本(text)需要通过编译器转化为应用,在计算机上运行
  • text–>binary,有两个过程:1.compiling,2.linking
  • cpp文件名并不做要求,只需告诉指明是cpp文件即可。ex:.cpp、.h(头文件),还可以自定义.world等等。
  • cpp文件和translation unit的区别与联系:一个cpp文件中可以包含(include)很多cpp文件,编译该cpp文件时,只会产生一个obj文件,称作translation unit;在一个项目中如果存在的都是独立的cpp文件,编译时每个文件产生一个obj文件,有多个translation unit。
  • 井号开头的语句都是preprocessor statement(预处理指令),编译器首先预处理这些指令,将相关代码复制粘贴到文件中。ex: ifdef、pragma、include、define等。
  1. 预处理preprocessing

ex1:

初始代码Math.cpp:

int Multiply(int a,int b)
{
	int result = a * b ;
	return result;
}

新建头文件EndBrace.h:

}

初始代码改为:

int Multiply(int a,int b)
{
	int result = a * b ;
	return result;
#include "EndBrace.h"

编译成功。

preprocessor to a file: output目录(debug文件)生成.i文件,从该文件中可以看到.h中的内容被复制粘贴到了Math.cpp中。

ex2:

#define INTEGER int
INTEGER Multiply(int a,int b)
{
	INTEGER result = a * b ;
	return result;
}

一切如常

改为:

#define INTEGER cherno
INTEGER Multiply(int a,int b)
{
	INTEGER result = a * b ;
	return result;
}

则预处理文件.i

#define INTEGER cherno
cherno Multiply(int a,int b)
{
	cherno result = a * b ;
	return result;
}

Math.cpp:

#if 1
int Multiply(int a,int b)
{
	int result = a * b ;
	return result;
}
#endif

.i 文件中并没有出现if和endif的语句

而改为:

#if 0
int Multiply(int a,int b)
{
	int result = a * b ;
	return result;
}
#endif

vscode中代码段变灰,.i 文件中没有代码

ex3:

#include <iostream>

iostream文件同样include其他文件,生成的.i 文件中代码多达五万多行,生成的obj文件很大。(生成了.i 文件就不会再生成.obj文件了)

  1. 将代码编译为机器码
  • .obj文件中为二进制码,其中一部分就是函数调用时,cpu所运行的机器码

设置output file >> assembly-only listing, 编译产生.asm文件,这就是obj文件的可读版本。该文件包含了汇编代码,这是运行函数时cpu执行的真正指令

演示的函数最终返回了result变量,所以在文件中,mov了两次,可以看出该代码可以优化的,可以写作

int Multiply(int a, int b)
{
	return a * b;
}

这样编译时间更快。或者可以将debug环境换成release环境。properties >> optimization >>maxmized speed, 会因为O2和RTC不兼容得到一个错误。这时候在code generation >> basic runtime checks 设置为default,也就是不运行行时检查(runtime)。这是编译器插入一些代码帮助我们debugging。

#include <iostream>
void Log(const char* message)
{
	std::cout << message << std:: endl;
}

int Multiply(int a, int b)
{
	Log("Multiply");
	return a * b;
}

如果调用函数但并没有使用,编译器将整段代码移除。调用函数时,汇编代码生成一个call指令,通过@+随机符号组成的函数签名辨认调用。

  1. 多个cpp文件通过linker连接生成一个exe文件。

How the c++ linker works

  • linking(链接)是从c++源码到为二进制码的过程。当translation unit被编译成一个个obj文件时,它们之间并没有联系。源文件被编译好了之后,进入了链接的过程。链接就是找到符号和函数的位置并将它们联系在一起。
  • main函数是编译的入口,从该函数开始执行代码,需要linker(链接器)将主函数与其他文件连接起来

Math.cpp:

#include <iostream>
void Log(const char* message)
{
	std::cout << message << std:: endl;
}

int Multiply(int a, int b)
{
	Log("Multiply");
	return a * b;
}#Multiply函数中引用了Log函数,控制台输出了语句,返回a*b。

编译实际上有两个阶段:编译和链接。在vs中,按下ctrl+F7或者编译按键只有编译而没有链接;按下build或者F5生成整个项目,则两者都有。

按下编译按键,没有报错,产生了obj文件。按下build按键,出现一个链接报错LNK:entry point must be defined。因为我们要将项目编译为一个应用程序(application exe)(出现语法报错是C开头)。每个exe文件都要有一个入口,在linker >> advanced 中可以自定义入口点,意味着入口不一定是main函数。

#include <iostream>
void Log(const char* message)
{
	std::cout << message << std:: endl;
}

int Multiply(int a, int b)
{
	Log("Multiply");
	return a * b;
}
int main()
{

}

添加main函数之后,不再报错,生成exe文件。

#include <iostream>
void Log(const char* message)
{
	std::cout << message << std:: endl;
}

int Multiply(int a, int b)
{
	Log("Multiply");
	return a * b;
}
int main()
{
	std::cout << Multiply(5, 8) << std:: endl;
	std::cin.get();#控制台不会瞬间关闭
}

将Log函数从Math.cpp移出单独作为一个cpp文件

#include <iostream>
int Multiply(int a, int b)
{
	Log("Multiply");
	return a * b;
}
int main()
{
	std::cout << Multiply(5, 8) << std:: endl;
	std::cin.get();
}

得到一个报错C开头,意味着这是语法错误。显示Log函数未被定义。加上Log函数的声明(不含函数的body):

#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();#控制台不会瞬间关闭
}

编译正确,build整个项目出现语法错误,Log.cpp中不包含头文件。加上语句#include<iostream>后成功。

如果改变Log.cpp中Log函数名为Logr,仍然可以编译成功,因为此时还没有linking阶段;但build整个项目会出现报错”未被定义的函数名(undefined external symbol)“。

此时,将Log("Multiply");注释,可以build成功。但如果将std::cout << Multiply(5, 8) << std:: endl;注释,则又会报错LNK,因为不能保证在其他地方不引用Multiply函数,而其中Log是未被定义的,所以仍然报错链接。

可以将函数前加static,意味着该函数只是为该翻译单元声明的,可以编译成功。函数的类型、返回值、参数个数都有严格规定,不可以随意更改,否则会报错LNK。还有函数必须是独一无二的,一个函数只能有一个body,两次被定义的相同函数也会报错(在不同cpp文件中LNK,在同一个cpp文件中C)。

定义Log.h:

#pragma once

void Log(const char* message)
{
	std::cout << message << std:: endl;
}

Math.cpp

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

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.cpp

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

void InitLog()
{
	Log("Initialized Log");
}

cpp文件都包含了一次Log.h,所以对于整个项目来说,Log.h包含了两次,报错LNK。

解决方法:将Log.h中函数定义为static void Log(const char* message)意味着对这个Log函数连接时,链接只发生在该文件的内部,只对该文件的内部有效,Log.cpp和math.cpp都有自己版本的Log函数。

或者inline void Log(const char* message),意味着把函数的body拿过来取代调用,

void InitLog()
{
	Log("Initialized Log");
}v

变为

void InitLog()
{
	std::cout << Initialized Log << std:: endl;
}

或者将Log函数复制到第三个翻译单元/复制到其中一个翻译单元中

  • 链接器就是要编译过程中生成的所有对象链接起来,包括一些可用库,比如c运行时库,c++标准库,平台API等。
  • 链接还包括静态链接和动态链接。

Variable in c++

整数

include<iostream>

int main()
{
    int variable = 8 ;
    std::cout << variable << std endl;
    variable = 20 ;
    std::cout << variable << std endl;
    std::cin.get();
} # 输出8和20

#int: 一个整数占有4个字节byte,一个字节是8比特bit的数据,一个整数有32比特的数据。符号占一位,剩下31位是大小。则整数的范围是正负2的31次方-1(还有0占一位)。

#unsigned int: 没有符号的整数,范围2的32次方。

#char(4字节),short(2字节),int(4字节),long(4字节depends on编译器),long long(8字节)。可以在前面添加unsigned得到更大的数字。

include<iostream>

int main()
{
    char a = 'A';
    std::cout << variable << std endl;
    std::cin.get();
} #输出A
include<iostream>

int main()
{
    char a = 65;
    std::cout << a << std endl;
    std::cin.get();
} #输出A(在Ascii码中,数字65代表A),cout根据char输出字符而不是数字
inlude<iostream>

int main()
{
    short a = 'A';
    std::cout << a << std endl;
    std::cin.get();
} #输出65

#数据类型之间的唯一区别是,用数据类型创建一个变量时,将分配多少内存


小数

  • 数据类型:float(4个字节), double(8个字节),bool(布尔类型,1个比特,定义true或者false)
inlude<iostream>

int main()
{
    float variable = 5.5;
    std::cout << variable << std endl;
    std::cin.get();
} #输出5.5,实际上是double而不是float。若要定义float,需要在5.5后面添加字母f或者F
inlude<iostream>

int main()
{
    bool variable = true;
    std::cout << variable << std endl;
    std::cin.get();
} #输出1。bool:除了0以外的任何数字都为true

#bool只占1个比特,而储存的最小单位为1个字节,所以8个bool数据可以储存为一个字节

inlude<iostream>

int main()
{
    std::cout << sizeof(int) << std endl;
    std::cin.get();
} #输出4

#操作符sizeof()可以得到数据类型的内存

  • 以上为数据的原始类型,高级:指针pointer*,引用reference&。

Fuctions in c++

include<iostream>

int Multiply(int a, int b) #函数接受两个参数
{
	return a * b;	#返回一个整数
}
include<iostream>

int Multiply()
{
	return 8 * 5;	#同样是返回一个整数的函数
}
  • 或者定义一个不返回任何值而仅仅执行的函数类型void,在控制台打印
include<iostream>

void Multiply() #函数接受两个参数
{
	std::cout << 5 * 8 << std::endl; 
}
  • 或者调用函数
include<iostream>

int Multiply(int a, int b) 
{
	return a * b;	
}

int main()
{
	int result = Multiply(5, 8); #用两个参数调用函数
	std::cout << result << std::endl; #将返回值的结果打印
	std::cin,get();
}
  • 打印多个值用void:
include<iostream>

void MultiplyAndLog(int a, int b) 
{
	int result = a * b;	
	std::cout << result <<std::endl;
}

int main()
{
	MultiplyAndLog(8 * 5);
	MultiplyAndLog(6 * 5);
	MultiplyAndLog(8 * 50);
	std::cin,get();
}
  • 函数的目的是防止代码重复。尽量将代码拆解为多个函数以便维护、理解等。但不要太over,因为函数的调用需要时间。原理:每一次调用函数时,编译器会生成一个调用指令,也就是每一次运行时的调用会让我们为这个函数创建一个栈框架(stack frame),将参数和返回地址推到栈上,跳到程序的不同部分以执行函数的指令,最后到最初调用函数地方。(默认为保留函数作为函数而非内联inline它)。
  • 带有返回类型的函数需要返回值。主函数且只有主函数不用返回值,主函数中不写返回值时默认为0。但只有在debug(调试)模式下会报错,在release模式下不会报错,如果不拿返回值做些什么的话才是对的,否则是未定义行为。在调试模式下,当特定的调试编译标记(flags)被激活时,将得到错误。

C++ header files

  • preprocessor语句include的作用是copy and paste 头文件的内容到程序中
  • Main.cpp
#include<iostream>
#include"Log.h"

int main()
{
	InitLog();
	Log("helloworld!");
	std::cin.get();
}
  • Log.cpp

    #include"Log.h"
    #include <iostream>
    
    
    void InitLog()
    {
    	Log("Initializing Log");	
    }	
    
    
    void Log(const char* message)
    {
    	std::cout << message << std::endl;
    }
    
  • Log.h

    #pragma once
    
    void Log(const char* message);
    void InitLog();
    
  • #pragma once的作用是只include一次头文件,被称为头文件保护符(header guard),防止把头文件多次include到一个翻译单元中(一个cpp文件中)

    若头文件1包含程序2,程序3包含所有,没有头文件保护符则会报错

    Common.h

    #include"Log.h"
    

    若此时在Main.cpp中添加头文件include"Common.h"没有头文件保护符将会报错

  • 还有一种防止头文件重复的方法是#ifndef …… #endif

example:

#ifndef _LOG_H
#define _LOG_H
void InitLog();
void Log(const char* message)
{
	std::cout<< message << std::endl;
}
#endif

将该段复制到Main.cpp中并注释头文件,编译成功;复制两次,第二段变灰。该语句检查_LOG_H是否被定义,如果被定义了,将把中间所有的代码include;否则禁用。

  • <>和""的区别

    编译器搜索路径不同:<>编译器所有include文件,““该文件的相对位置,如:存在于该cpp的上一级目录中,可以写作#include”…/Log.h”。""可用于所有,<>只搜索编译器的include路径

  • 扩展名+.h和不加的区别:c标准库和cpp标准库的区别

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值