08-09变量、函数

变量 #

  • 编程实际上就是在操纵数据
  • 变量允许我们命名一个我们存储在内存中的数据
  • 我们创建的变量存储在堆栈或堆中
  • 原始数据是构成任何种类的基石
  • 不同的变量类型,唯一的区别,是存储他们需要多少内存(原始数据类型有多大)

整数 #

#include <iostream>

int main() {

	int variable = 8;
	std::cout << variable << std::endl;
	variable = 20;
	std::cout << variable << std::endl;
	std::cin.get();
}
  • int变量通常上是4字节,取决于编译器
  • 可以不用立即赋值
  • 大概在-20亿~+20亿之间
  • 4字节是32位,int是有符号的,第32位存储符号位。还剩下31位存储数据。2^31=21 4748 3648 。
    • 最大正数:2^31-1=21 4748 3647
    • 最小负数:-(2^31)=21 4748 3648
    • 为什么负数多一个,因为多了一个"-0"但计算机中没有-0的概念,二进制10000000 00000000 00000000 00000000 代表-(2^31)
  • unsint 表示无符号整数,能用上所有的位,即最大整数是(2^32) -1
  • 其他:char 1字节,short 2字节,int 4字节,long 通常4字节,long long 通常8字节 由于32位系统最大是4字节,所以long也是4字节。后来出了64位系统,64位windows系统中long是4字节,64位Linux/macOS中long是8字节 可以给任何一个前面加unsigned表示无符号

	char cVariable = 'A';
	//char cVariable=65;
	//不管是用65还是'A'赋值,打印出来的都是A
	std::cout << cVariable << std::endl;
	
	
	short sVariable = 'A';
	//short cVariable=65;
	//不管是用65还是'A'赋值,打印出来的都是65
	std::cout << sVariable << std::endl;

小数 #

	float fVariable = 8.2f;//4字节
	std::cout << fVariable << std::endl;
	double dVariable = 20.3;//8字节
	std::cout << dVariable << std::endl;

布尔值 #

布尔类型只占用1个字节(只需要1位,但我们在处理内存寻址时,即我们要从那里取回我们的值时,或者将它存储在内存体重时,没有办法只处理个别位,所以只能按字节取)

0表示假,除了0以外的任何值表示真

	bool bVariable = true;
	std::cout << bVariable << std::endl;//1
	bVariable = false;
	std::cout << bVariable << std::endl;//0
	bVariable = 322;
	std::cout << bVariable << std::endl;//1
	std::cin.get();

数据类型大小 #

检查变量占用多少字节

	std::cout << sizeof(bVariable) << std::endl; //1

不要假设某个数据类型是多大,而是明确指出需要几个字节的数据类型,例如

    // 固定宽度有符号整数
    int8_t  small;      // 明确8位,范围:-128 ~ 127
    int16_t medium;     // 明确16位,范围:-32768 ~ 32767
    int32_t standard;   // 明确32位,范围:-2.1e9 ~ 2.1e9.2e18 ~ 9.2e18
    
    // 固定宽度无符号整数

    int64_t large;      // 明确64位,范围:-9.2e18 ~ 9.2e18
    
    // 固定宽度无符号整数
    uint8_t  u_small;   // 0 ~ 255
    uint16_t u_medium;  // 0 ~ 65535
    uint32_t u_standard;// 0 ~ 4.3e9
    uint64_t u_large;   // 0 ~ 1.8e19

函数 #

  • 函数的基本构成是代码块,编写函数的目的是稍后执行特定任务时,进入那些被称为“块”的东西
  • 函数具有输出和输入
  • 当我们每调用一个函数时,编译器生成调用指令,意味着每次执行函数需要创建函数需要的堆栈框架,里面包括参数、返回地址之类的东西;之后跳转到实际的不同部分的代码块(二进制文件)执行,之后返回需要获取的值 所以有时候需要内联函数
  • 普通函数(非main函数)必须显示指定返回值 (return xxx;),main函数如果没指定,会默认return 0;

例子 #

#include <iostream>

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

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

int main() {

	MultiplyAndLog(3, 2);
	MultiplyAndLog(8, 5);
	MultiplyAndLog(90, 45);


	int result = Multiply(3, 2);
	std::cout << result << std::endl;


	int result2 = Multiply(8, 5);
	std::cout << result2 << std::endl;


	int result3 = Multiply(90, 45);
	std::cout << result3 << std::endl;

	std::cin.get();

}

头文件 #

  • 头文件传统上是为了声明某些类型或函数确实存在
    • 假设我们在一个文件中创建了一个函数、我们想在另一个文件中使用它,如果不提前声明那么就无法识别
    • 我们需要一个共同的地方来仅声明(而非定义)这个函数确实存在,然后就可以给其他文件include了(也不会导致重复定义)

例子 #

//Log.h
#pragma once
void Log(const char* message);
void InitLog();//必须声明,因为Main10.cpp中调用了该函数
#include <iostream>
#include "Log.h" //必须include用以声明,否则会提示 'Log': identifier not found

void InitLog() {
	Log("Initializing Log");//!!!没有include Log.h这里会报错
}

void Log(const char* message) {
	std::cout << message << std::endl;
}
//Main10.cpp
#include <iostream>
#include "Log.h"

int main() {
	InitLog();//调用了Log.app中的函数
	Log("hello world!");
	std::cin.get():
}

pragma #

  • #pragma once 称为预处理命令或预处理指令
  • pragma是发给编译器或预处理器的指令,once意味着只包含(include)此文件一次,阻止我们包含同一个头文件多次放入同一个单个翻译单元
  • 如果x.h包含了y.h,a.cpp又包含了x.h和y.h,如果y.h中写了#pragma once,则y.h不会被包含两次

视频中举了个例子,比如在test.h中包含了struct Name {}; 而a.cpp和b.cpp都#include test.h。为什么这里允许(不同文件中存在)结构体定义重复,不允许函数定义重复?

  • 结构体(类型信息)不生成链接符号:结构体定义只告诉编译器"Name类型有多大,有什么成员"
  • 本地信息:每个.obj文件只需要知道如何处理Name类型的变量
  • 不需要链接器参与:类型信息不参与链接过程

HeaderGuards #

Header guards(头文件保护)是C/C++中防止头文件被多次包含的预处理技术。

修改Log.h头文件

//Log.h 
//#pragma once
#ifndef _LOG_H
#define _LOG_H

void Log(const char* message);
void InitLog();

struct A {};

#endif

include <>include "" 区别 #

尖括号-搜索顺序 #

  1. 编译器内置路径
  2. 系统环境变量指定的路径
  3. -I 指定的路径

双引号-搜索顺序 #

  1. 当前文件所在目录(还可以使用相对目录,比如include "../Log.h"
  2. 尖括号的所有搜索路径

最佳实践 #

  • 标准库、第三方库:使用 <>
  • 项目自己的头文件:使用 ""
  • 当不确定时:通常用 "" 更安全,因为它会先搜索当前目录
  • C标准库通常有.h结尾,例如#include <stdlib.h>
  • C++标准库一般没有后缀,例如#include <iostream>