c++中,用于编写代码的文件为 .cpp 文件(类似于.java文件)
// 这句话是预处理的操作,在源代码被编译前,替换文件
// 相当于 Java 种的 import,导入了一个java文件
// 中定义了 C++的输入与输出(相当于IO流)
#include
// using 是一个编译指令,namespace 用来指定命名空间
// 此命名空间与 Vue 的类似,也相当于 Java 的包名(浅显理解)
using namespace std;
// C++ 的程序必须含有一个 main 函数作为入口
int main() {
int age = 18;
// 这里是声明了一个引用 ref,并且将 age 与 ref 绑定起来
int& ref = age;
ref += 20;
// cout 是输出流,cin 是输入流
// 如果上面么有使用 std 命名空间
// 这里就可以书写成: std::cout 表示我要使用std里的cout函数
// << 表示将值传给谁,箭头指向表示值的流向
// 使用 cin 就是 cin >> ref
// 上述含义: 接收一个命令行输入的值,赋值给 ref 变量
cout << ref;
}
c++中的基本数据类型,有算术类型和空类型
算数类型分为整型和浮点型。
整型:
算术类型 | 字节位 | 备注 |
---|---|---|
short | 至少16位 | |
int | 至少与short一样大 | |
long | 至少32位 | 需 ≥int |
long long | 至少64位 | 需 ≥long |
char | 8位 | |
bool | 未定义 | 就是Boolean类型 |
浮点型:
浮点型 | 字节位 | 备注 |
---|---|---|
float | 至少32位 | |
double | ≥48 | 至少 ≥float |
long double | ≥double |
无符号类型:类型前面加上 unsigned 即可表示无符号类型,无符号类型表示的最大值是有符号类型的两倍。(假设同样的内存空间,有符号类型可表示 -10 到 10。那么不带符号,也就不用表示负数,表示负数的空间就可以用来表示更大的整数,0-20)
char:默认既不是有符号,也不是无符号。(与符号无关)
可以在 c++ 代码中显示设定: signed char jeep;
unsigned char bar;
扩展字符型:
扩展字符型 | 字节位 | 备注 |
---|---|---|
wchar_t | ≥16 | 宽字符 |
char16_t | ≥16 | Unicode 字符集,c++11新增 |
char32_t | ≥32 | Unicode 字符集,c++11新增 |
注意项:
JS将 非0,非null,非undefined看成true,Java 中的if()只能接收一个boolean类型。
c++ 将 非0 看作 true。将 true 看作1。
// 对于基本数据类型,c++ 会给 i 一个默认值0,那么也就给 i 分配了一个内存空间
int i;
// 下面代码,只是单纯的声明了 j ,不会给默认值0,也不会给 j 分配内存空间
// 这就是声明而非定义
extern int j;
c++ 中的符合类型包括:数组,字符串,枚举,结构体,共用体,引用,指针。
数组、字符串、枚举 与 Java 的差不多。
// 通过 struct 关键字,定义了一个结构体 Cat
// 数组只能存储同一个类型的数据,当我们需要存储不同类型数据的时候就可以使用 结构体
// c++的结构体和类都可以存储不同类型的数据。
// c++的结构体和类的区别在于某些默认权限修饰符的不同(暂时理解)
struct Cat
{
char name[20];
int age;
}
// 如下,定义了一个 Cat 类型的结构体,变量名为 tom
Cat tom = {
"tom",
3
};
共用体的作用:在相同的内存位置存储不同的数据类型。
一个共用体中可定义多个不同类型的成员,但任何时候只能有一个成员含有值,共用体提供了一种使用相同内存位置的有效方式。
和 TS 中的联合类型很相似
// 通过 union 关键字,定义了一个共用体 Animal
union Animal
{
Cat cat;
Dog dog;
}
引用相当于变量的别名,是对一个已经存在的对象起的另一个名字。(引用与对象之间是绑定到一起的)
// 定义一个 int 类型的变量 age 赋值 1
int age = 1;
// 定义了一个 int 类型的引用 ref,将 age 与 ref 绑定
int& ref = age;
注意项:
有啥用呢?
#include
using namespace std;
// 一个简单的 交换两个数的函数
void transform(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int aa = 10;
int bb = 20;
transform(aa, bb);
cout << aa << endl;
cout << bb << endl;
// 实际上输出的结果是 aa=10 bb=20
// 这是因为 c++ 是值传递
}
// 一个简单的 交换两个数的函数
void transform(int a, int b) {
// 当调用 transform 时,传入了 aa,bb 的值
// 使得 a = 10, b = 20,可以理解为初始化了两个新的变量
// 这两个新变量只是内容和
int temp = a;
a = b;
b = temp;
// 执行完上述三行代码后,仅仅只是 a 和 b 的值互相交换了,
// 并不会影响函数外的 aa bb
cout << "a=" << a << endl;
cout << "b=" << b << endl;
// 输出的结果是 a=20 b=10
}
// 将函数声明中的参数列表更改为 (int& a, int& b)
// 这里接收的参数就是引用
// 当我们传入 aa 和 bb 时,就是将 引用 a 和 aa 绑定,引用 b 和 bb 绑定
// 当转换 a 和 b 时,就转换了 aa 和 bb
void transform(int& a, int& b) {
//xxx
}
Java,JS 也是值传递,Java,JS 执行上述类似代码,结果是一样的,不会更改方法外面 aa bb 的值。
值传递:
首先记录一下形参和实参。参数列表上的参数,称作形参(a和b)。传入函数的值,称作实参(aa和bb)。实参是形参的初始值。
什么是值传递? 形参是实参的拷贝,改变形参的值不会影响外部实参的值。
想改变值传递,可以使用引用作为形参,接收实参,操作引用就可达到操作实参的目的。
易混淆点:
Java,JS 中的对象作为实参传入到函数的参数列表中,在函数中,调用对象的方法、属性,更改了对象中的某个属性的值。这种操作,函数外面的实参,是会变化的。这和上面描述的场景是两回事!!
调用对象的方法、属性,形参指向的目标不会变。但是通过等号进行的赋值操作,会使形参的指向发生变化。
引用需要注意的点:
(曾经只会Java的我一直以为指针和引用是一个东西。)
指针也是复合类型变量的一种,英文名为 pointer。与引用类似,指针也实现了对其它对象的间接访问,但是指针和引用相比有一些不同点。
// 使用 * 定义指针,下面代码就定义了一个 int 类型的指针 ip
int* ip;
// 定义一个 int 类型的变量 temp
int temp = 2;
// 将变量 temp 的地址赋值给指针 ip
// 在这里 & 称作 取地址符
ip = &temp;
// 这里输出的值为 2
// * 在这里使用,被称作 解引用符
// ip 指的是上面那个 叫 ip 的指针,*ip 才是 ip 指针指向的地址(就是temp的地址)
// 所以通过 *ip 就能找到 temp
// 使用 *ip 相当于使用变量 temp
cout << *ip;
&、*不同用法的含义记录:
符号 | 紧随类型名 | 出现在表达式中 |
---|---|---|
& | 声明引用 | 取地址符 |
* | 声明指针 | 解引用符 |
void类型的指针:
引用和指针最大的区别:
可以给指针重新赋值,指向一个新对象。
引用不能重新赋值。
指向指针的指针:
通过 * 的数量来区分指针的级别。
**p 表示指向指针 *p 的指针
***p 表示指向 (指向指针 *p 的指针**p)的指针
简单了解并没有验证的内容:
C 中开辟内存的函数为 malloc()
。
C++ 中使用 new
关键字即可(也可使用 malloc()
)。
C++ 中没有垃圾回收机制。(Java 中的GC)
// 开辟了一个可以存放 int 类型变量的内存空间
int* pn = new int;
// 释放该空间
delete pn;
// 开辟了一个可以存放 int 类型数组的内存空间
int* pn = new int[10];
// 释放 pn 数组的空间
delete[] pn;
注意项:
使用指针的时候,要注意使用 * 解引用符得到真正的值。
const
是 Java 中的保留字,不是关键字。在 Java 中并没有使用到 const
的地方,只是也不让我们用而已,这种 Java 不用也不让我们用的,叫做保留字。 java 用了,不让我们用的,叫关键字。
C++ 中 cosnt
修饰的变量不能更改,且必须初始化。(最近用 Vue3 + TS,猛写 const
)
const
修饰的变量不能更改,const
修饰的类、结构体、其成员也不能改变。
struct TestStruct
{
char name[20];
int age;
};
void test() {
const TestStruct temp = {
'name',
20
};
// 下面这句会报错
// 因为 temp 被 const 修饰,所以不可更改 age 属性
temp.age = 30;
// 因为变量 temp 被 const 修饰,则指向变量 temp 的指针也应该用 const 修饰
const TestStruct* pT = &temp;
// 指针pT 调用 name 属性的话,使用 ->
}
函数分为两类:
C++ 中的代码是从上到下顺序执行的。
int main() {
test();
}
void test() {
int a = 20;
cout << a;
}
上述代码会报错,找不到 test() 函数,因为当 main
函数执行时,test
函数还未被加载。
解决方式,可以将 test
函数写在 main
函数上方,也可以使用 函数声明
。
函数声明
函数声明也叫做函数原型,函数声明和函数的定义类似,唯一的区别是函数声明不需要函数体。
(一直不太清楚前端TS中有时会需要写 .d.ts 文件是在干嘛。)
// 这里就是函数的声明
void test();
int main() {
test();
}
void test() {
int a = 20;
cout << a;
}
函数声明一般都放在头文件 .h
中,而不是放在源文件 .cpp
中
// func.h 文件中内容
void test();
// 使用 func.h 需要先 include
#include "func.h"
int main() {
test();
}
void test() {
int a = 20;
cout << a;
}
默认参数
默认参数指的是函数调用时省略了实参时,自动使用的参数。
内联函数
使用 inline
关键字修饰函数的声明或实现,可以使其变为内联函数。
内联:将函数的调用直接展开为函数体,作为代码插入调用位置。
inline
关键字:向编译器申请,将该函数内联,但是编译器会做出检测,判断该函数是否可以内联,若检测出该函数体量大,流程繁琐,则不会内联该函数。递归就不会被内联。
什么是函数调用的开销?
当我们调用了一个函数,会先开辟栈空间,当调用完毕后,该空间被回收。从开辟空间到回收空间的过程的消耗,称为函数调用的开销。多次调用某一函数时,就会频繁的开辟,回收。将函数置为内联函数,就可以避免这部分开销。
内联函数使用场景:适合用于优化那些规模较小、流程直接、频繁调用的函数。
优缺点:内联函数可以避免函数调用的开销,但是会增加代码行数。