C/C++基础学习

环境搭建

windows环境基础搭建:这个不介绍一路下一步就行

linux环境基础搭建:

我是搭建在Ubuntu上的:

1
2
3
4
5
6
7
8
9
10
11
#首先更新一下源
sudo apt update

#安装一个文本编辑器
sudo apt install vim

#安装gcc
sudo apt install gcc

#安装g++
sudo apt install g++

让我来写第一个代码并将其编译看看是否已经下载完毕:

Hello.c:

1
2
3
4
5
#include <stdio.h>
int main(){
printf("Hello World");
return 0;
}

编译.c文件成可执行文件:

gcc -o hello hello.c

执行这个可执行文件:

因为我们的Ubuntu系统是64位的系统,所以他也默认给我们生成了64位的文件。

我们可以下载如下库这样就支持我们编译成32位的文件了

1
2
sudo apt install gcc-multilib
sudo apt install g++-multilib

编译成32位文件:

gcc -m32 -o hello32 hello.c


第一个c++代码:

hello1.cpp

1
2
3
4
5
6
#include <iostream>

int main(){
std::cout << "Hello World!" << std::endl;
return 0;
}

编译c++代码:

g++ -o hello1 hello1.cpp


上述做好以后我们还需要下载一个调试器

1
2
3
sudo apt install gdb

sudo apt install nasm

然后再安装一个类似于VScode的IDE

这里我选择破解clion好用放在tool这个目录下了。

破解过程网上都是教程。

至此linux环境就配置完毕了。

第一个C&C++程序

创建项目步骤:

新建——>文件——>新建项目

选择空项目——>给项目命名,选择项目路径

右键并点击属性:

一一对应:当然平台也可以直接选择所有平台

Debug是调试版,release是发行版

后续运行代码如果出现问题可以看看这两个设置有没有设置出问题:

解读这个小程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
printf("Hello World!\r\n");
return 0;
}


//main 程序的主函数,可以理解为自己写的代码从哪里开始执行。

//int 保留字,基本的数据类型之一,整数
//放在函数之前就是 返回值 的数据类型

//函数就是一个功能的聚合,可以由多行代码进行组合,函数可以具备参数与返回值

//return 返回 其后面跟随的值,必须与返回类型相同

printf也是一个函数,我们直接点击其然后F12跟进查看:其函数相关定义:

这个函数是一个什么函数呢?

printf是一个库函数,c/c++提供给我们的函数就是库函数,库函数之所以称之为库函数,是因为调用这些函数需要依赖于c/c++的运行库(runtime)

可以自己写一个头文件内容

1
2
3
#pragma once
//变量类型 变量名 = 值
int test = 12138;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include "main.h"

//main 程序的主函数,可以理解为你自己写的代码从哪里开始执行
//int 保留字,基本数据类型之一,整数
//返回值
//函数 就是一个功能的聚合,可以由多行代码进行组合,函数可以具备参数与返回值
//return 返回 他后面跟随值,必须与返回类型相同
//printf库函数 c/c++提供给我们的函数就是库函数,库函数之所以称之为库函数,是因为调用这些函数需要依赖于C/C++的运行库(runtime)
//printf 输出函数(打印函数)
//stdio.h里面保存了printf的声明
//#include 预处理机制

/*
#include
<>包裹的一般是库目录
""包裹的一般是当前目录或第三方库
*/


//预处理 编译 汇编 链接
/*
预处理
gcc -E hello.c -o hello.i
编译
gcc -S hello.i -o hello.s
汇编
gcc -c hello.s -o hello.o
链接
gcc hello.o -o hello
*/
int main()
{
printf("Hello World!\r\n");
printf("%d\r\n", test);
test = 123456;
printf("%d", test);
return 0;
}

输出如下:

一个c代码在运行时会经历如下几步:

这里用ubuntu展示会更加清晰:

预处理 编译 汇编 链接

  • 预处理
    gcc -E hello.c -o hello.i

预处理相当于是把头文件中的声明,代码全部粘贴到当前文件中来:

上面的实际上就是<stdio.h>当中的东西,直接复制到了这里

我们自己写的代码已经在非常非常后面了

  • 编译
    gcc -S hello.i -o hello.s

编译将我们的c代码编译成了汇编语言:

  • 汇编
    gcc -c hello.s -o hello.o

汇编之后就形成了elf文件,没有把支持库放进去现在里面是缺东西的

  • 链接
    gcc hello.o -o hello

将一些必须库都给加载进去


进制与位

进制:

1
2
3
4
5
6
7
8
9
10
11
2进制:0 1 10
#意味着个位上能表示2个数 0 1

8进制:0 1 2 3 4 5 6 7 10
#意味着个位上能表示8个数 0 1 2 3 4 5 6 7

10进制:0 1 2 3 4 5 6 7 8 9 10
#意味着个位上能表示10个数 0 1 2 3 4 5 6 7 8 9

16进制:0 1 2 3 4 5 6 7 8 9 A B C D E F 10
#意味着个位上能表示16个数

位:

1
2
3
4
5
6
7
8
9
10
11
12
位:
QWORD,DWORD,WORD,BYTE指的是数据宽度的表示
QWORD 64位
DWORD 32位
WORD 16位
BYTE 8位(1字节)

地址:
地址长度:
x86 32 : 32位 0xFFFFFFFF
x64 64 : 64位 0xFFFFFFFFFFFFFFFF

这是64位:

这是32位:

这是16位:

这是8位:

基础数据类型

常见数据类型

整数类型(基于MSVC)

整数类型概览

类型 宽度(字节) 无符号范围(unsigned) *有符号范围(signed)*
short 2 0 ~ 65,535 -32,768~+32,767
int 4 0 ~ 4,294,967,295 -2147483648~+2147483647
long 4 0 ~ 4,294,967,295 -2147483648~+2147483647
Long long 8 0~18446744073709551615 -9,223,372,036,854,775,808~+9,223,372,036,854,775,807

默认的都是有符号整数

直接将首位符号位设置为0他就是正数:32767

如果是32位的我们全1:他就是65535

字符类型

类型 宽度(字节) 无符号范围(unsigned) 有符号范围(signed)
char 1 0 ~ 255 -128 ~ 127
wchar_t 2 0 ~ 65,535 -32,768~+32,767

浮点数类型

类型 宽度(字节) 范围 别称
float 4 -3.4E+38 和 3.4E+38 单精度浮点数
double 8 -1.7E-308~1.7E+308 双精度浮点数

练习代码如:

main.h:

1
2
3
4
#pragma once
extern int g_Number;
//变量类型 变量名 = 值
//int test4 = 12138;

test.cpp:

1
2
3
4
5
6
#include "main.h"

void test2()
{
g_Number = 15;
}

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include "main.h"

//不在任何函数的作用域内{}
int g_Number = 12;


#define MAX 256

int rk()
{
return g_Number + 1;
}

int main()
{
short sNumberA = 0;
sNumberA = 0xFFFF;
short sNumberB = 2;
//= 赋值运算符,把右侧的表达式运算结果赋值给左侧的变量
short sNumber = sNumberA + sNumberB;
printf("\t%hd+%hd=%hd\r\n", sNumberA, sNumberB,sNumber);
int nNumber = 12138;
long lNumber = 123456;
long long llNumber = 123456789;
unsigned int unNumber = 158;
printf("\t%d,%d,%lld\r\n", nNumber, lNumber, llNumber);
float fNumber = 12.5f;
double dbNumber = 12.5;
printf("\t%f-\"%f\r\n", fNumber, dbNumber);
//大端序与小段序
int a = 0x12345678;
//大端序:12 34 56 78
//小端序 78 56 34 12

//const修饰符,所修饰的变量不可以改变
//const int csNumber = 1213;
//csNumber = 12;

int nNum = MAX;

//{
// int rk = 12138;
//}
int rk = 12138;
nNum = rk;
//ASCII占1字节 多字节
char cStr = '1';
//UNICODE编码 占2字节 宽字节
wchar_t wcStr = 'a';
printf("%c", wcStr);
return 0;
}

内存查看:(调试)

调试——>窗口——>内存——>内存1

直接&Abc利用取地址符

接下来分析一下这段代码内容:

首先打两个断点:然后运行这个项目:

在上面这一步中&Abc其还是一个cc cc的未初始化的一个值。

F10让其运行到下一步:

这个 红色的00 00就是他所占的一个内存空间

0x00B3FA78是他的内存地址

继续下一步:

这就是我们给他赋的值FFFF.

大端序和小端序:

1
2
3
4
int a = 0x 12345678;
//大端序: 12 34 56 78
//小端序:78 56 34 12

一般情况下默认内存存储为小端序

如下图我所打断点所示:

算术运算符号

练习代码:

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>

int main()
{
//+ 加法运算符
//- 减法运算符
//* 乘法运算符
////除法运算符
//= 赋值运算符
//+= -= *= /=
//变量类型 变量名 = 表达式
/*
int nAddRes = 1 + 2;
nAddRes += 1;// nAddRes = nAddRes + 1;
printf("nAddRes:%d\r\n", nAddRes);
int nSubRes = 1 - 2;
nSubRes -= 1;
printf("nSubRes:%d\r\n", nSubRes);
int nMulRes = 1 * 2;
nMulRes *= 1;
printf("nMulRes:%d\r\n", nMulRes);
int nDivRes = 1 / 2;
nDivRes /= 1;
printf("nDivRes:%d\r\n", nDivRes);
*/
//++--自增自减运算符
int nNunber = 10;
//nNunber++;//nNunber = nNunber + 1;
//nNunber--;//nNunber = nNunber - 1;
//前置运算与后置运算
//printf("%d\r\n", ++nNunber);
//printf("%d\r\n", nNunber);
//% %= 取模
//nNunber = nNunber % 3;
//nNunber %= 3;
//printf("%d\r\n", nNunber);
//sizeof 计算数据宽度
//printf("%d\r\n", sizeof(long long));
//bool bFlag = true / false;
//> >= < <= == != 比较运算符
bool bFlag = 1 != 2;




system("pause");
return 0;
}

位运算和逻辑运算

位运算——>main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>

int main()
{
//& | ~ (与,或,非)
//<< >> (左移,右移)

/*int nNumA = 0b11110011;
int nNumB = 0b10110011;
int nRes = nNumA & nNumB;*/
//&位与,两个数的同一位都是1,结果才为1,其他都是0
//0b11110011
//0b10110011
//---------------
//0b10110011
//0b10110011

/*int nNumA = 0b11110011;
int nNumB = 0b10110011;
int nRes = nNumA | nNumB;*/
//|位或 两个数的同一位只要有其中一位是1,那么结果就是1,两位都是0,结果才为0
//0b11110011
//0b10110011
//---------------
//0b11110011
//0b11110011

//~ 位非 把当前数的1逆转为0,把0逆转为1
//int nRes = ~0b10101010;

//<< 左移运算符,整体左移,低位补0
//int nNum = 0b00000001;
//int nRes = nNum << 5;

//>> 右移运算符,整体右移,高位是1就全补1,高位是0,就全补0
//char nNum = 0b11000000;
//char nRes = nNum >> 5;
system("pause");
return 0;
}

注意:

右移略微复杂:整体右移没有问题,但其不是高位补0,而是高位是1就全补1,高位是0就全补0

逻辑运算——>main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>

int main()
{
//逻辑运算符
//&& 逻辑与
//|| 逻辑或
//! 逻辑非
//三目运算符 ()?v:v
//bool bRes = (10 > 5) && (10 > 11);//true && true = true
//bool bRes = (10 > 11) || (10 > 12);
//bool bRes = false;
//bRes = !bRes;
//变量类型 变量名 = 表达式 ? TRUE:FALSE
int bRes = 10 > 20 ? 1 : 2;
system("pause");
return 0;
}

选择结构

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>

int main()
{
/*int nFlag = 1;
int nFalg2 = 2;
if (nFlag == 1 || nFalg2 == 4 || nFalg2 == 2)
{
printf("1\r\n");
}
else if (nFlag == 2)
{
printf("2\r\n");
}
else if (nFalg2 == 5)
{
printf("5\r\n");
}
else
{
printf("else\r\n");
}*/
//int nFlag = 1;
//switch (nFlag)
//{
//case 1:
//{
// printf("1\r\n");
// break;
//}
//case 2:
//{
// printf("2\r\n");
// break;
//}
//default:
// printf("default\r\n");
// break;
//}
Lab1:
int nFlag = 1;
goto Lab1;
nFlag = 5;

printf("%d\r\n", nFlag);
system("pause");
return 0;
}

循环结构

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>

int main()
{
//for while do while
//()表达式
//int nFlag = 0;
//while (nFlag <= 100)
//{
// //重复执行的代码
// printf("Hello %d\r\n",nFlag);
// nFlag++;
//}
//int nFlag = 0;
//do
//{
// printf("Hello %d\r\n", nFlag);
// nFlag++;
//} while (nFlag <= 100);

//for (int i = 0; i <= 100; i++)
//{
// printf("Hello %d\r\n", i);
//}


//for (int i = 0; i <= 100; i++)
//{
// for (int j = 0; j <= 100; j++)
// {
// printf("Hello %d\r\n", j);
// }
//}

system("pause");
return 0;
}

数组与字符串

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>

int main()
{
//一维数组
//某一类型变量的集合
//数组的内存是连续
//数组名称就是数组的首地址
//int nNumArr[10] = { 0 };
//for (size_t i = 0; i < 10; i++)
//{
// nNumArr[i] = i;
//}
//for (size_t i = 0; i < 10; i++)
//{
// printf("0x%X\r\n", nNumArr[i] * 10);
//}
//多维数组
//int arr1[] = { 0,1,2 };
//int arr2[] = { 3,4,5 };
//int arr3[] = { 6,7,8 };
//int *arr[3] = { arr1,arr2,arr3 };
//int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
//for (size_t i = 0; i < 3; i++)
//{
// for (size_t j = 0; j < 3; j++)
// {
// printf("%d\r\n", arr[i][j]);
// }

//}
//字符串
char szStr[] = "rkvir";
char szStr2[] = { 'r','k','v','i','r',0 };
wchar_t wszStr[] = L"rkvir";
printf("%s\r\n", szStr);
printf("%S\r\n", wszStr);
system("pause");
return 0;
}

指针和内存管理

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main()
{
//& *
//内存:变量 地址:值
//0x010FFEAC 00 00 00 00
//&nNum = 0x010FFEAC 地址
//00 00 00 00 值
//int nNum = 0;
//nNum = 0xFFFFFFFF;
////int * p
////*p = 值
////p = 地址
/*
实验查看:
&nNum:0x00AFF7CC
p:0x00AFF7C0 cc f7 af 00
发现其是小端序
*/
//int * p = &nNum;
//int* p2;
//p2 = &nNum;
//*p = 12138;
//*p2 = 0xEEEEEEEE;
//int nNumB = 0;
//p2 = &nNumB;
//*p2 = nNumB;
//int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
//int * p = arr;
//for (size_t i = 0; i < 10; i++)
//{
// printf("%d\r\n", *p);
// p++;
//}
//指针的加法和减法
//p = 数组首地址
//p++
//addr + sizeof(type)
//addr + 4
//addr + 1
//arr + 2
//1 2 3 4

//C&C++内存管理
//c malloc free
//void * szBuffer = malloc(100 * sizeof(int));
//memset(szBuffer, 0, 100 * sizeof(int));
//memcpy(szBuffer, "rkvir", 6);
//printf("%s\r\n", szBuffer);
//free(szBuffer);

////c++ new delete
//char *szBuffer = new char[100];
//memset(szBuffer, 0, 100);
//memcpy(szBuffer, "rkvir", 6);
//printf("%s\r\n", szBuffer);
//delete szBuffer;

char * szBuffer = (char *)"rkvir";

system("pause");
return 0;
}

内存:

变量存在地址这个东西

字符串操作函数

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

int main()
{
//ASCII
char * szStr = (char *)"rkvir";
//计算字符串长度
printf("String Length:%d\r\n", strlen(szStr));
//是否包含子串
char * szSubStr = (char *)"vi1";
if (strstr(szStr, szSubStr))
{
printf("包含\r\n");
}
else
{
printf("不包含\r\n");
}
char szCatStr[50] = { 0 };
//字符串拷贝函数
strcpy(szCatStr, "Hello");
printf("%s\r\n", szCatStr);
//字符串拼接函数
strcat(szCatStr, " World!");
printf("%s\r\n", szCatStr);
//对比
if (strcmp(szStr,"rkvir2") == 0)
{
printf("相同\r\n");
}
else
{
printf("不同\r\n");
}
char * szNum = (char *)"100";
int nNum = atoi(szNum);
printf("%d\r\n", nNum);
//UNICODE
wchar_t wcsStr[] = L"rkvir";
printf("%d\r\n", wcslen(wcsStr));

system("pause");
return 0;
}

函数与递归

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

/*
返回类型 函数名(参数列表)
{
代码体
return 返回值;
}

*/

//指针传参-字符串
void test1(const char * szBuffer)
{
printf("%s\r\n", szBuffer);
}

//指针传参-整数
void test2(const int * nNumArr,int nSize)
{
for (size_t i = 0; i < nSize; i++)
{
printf("%d\r\n", nNumArr[i]);
}
}

//指针返回
char * test3()
{
return (char *)"rkvir";
}

//参数返回
void test4(int *nRes)
{
*nRes = 100;
}

//函数指针
typedef void(*fntest5)(int *);

//递归调用
int test6(int nValue, int *nCount)
{
if (nValue <= 100)
{
*nCount += nValue;
test6(++nValue, nCount);
}
return *nCount;
}

int main()
{
//指针传参-字符串
test1("rkvir");
//指针传参-整数
int nNumArr[] = { 1,2,3,4,5,6,7,8,9 };
test2(nNumArr, 9);
//指针返回
printf("%s\r\n", test3());
//参数返回
int nRes = 0;
test4(&nRes);
printf("%d\r\n",nRes );
//函数指针
fntest5 funtest5 = (fntest5)test4;
funtest5(&nRes);
printf("%d\r\n", nRes);
//递归调用
int nRet = 0;
test6(0, &nRet);
printf("%d\r\n", nRet);
system("pause");
return 0;
}

F11快捷键跟进函数内部

想要在下方查看其指针指向的地址的内容就直接,指针名 回车即可:

这个就是其nNumber这个变量存储的地址:

0x008FF8E8

我们看看外部变量num的对应的地址是:

&num回车即可:

其对应的地址也是0x008FF8E8这也就是直接传入地址,然后函数内部直接对地址对应的值就可以进行操作了。

如何使用函数指针?

首先了解一下typedef:

其是用来定义类型别名的关键字

1
2
3
4
5
6
7
8
9
10
typedef int rkvir;

int main() {

rkvir a = 123;//rkvir就给当成int用了
printf("%d\r\n", a);
system("pause");
return 0;

}

函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
这就相当于是另外一个函数了,一个函数指针了
后面一个括号如果有参数,就把参数的类型写上去
*/
typedef int(* _math1)(int);
//又或者typedef int (* _math1)(int);

int math(int num) {

return num;
}

int main() {

/*
声明一个函数指针的对象
用这个对象等于你要获取的函数地址,函数名就是函数地址
*/
_math1 fn = math;
int nRes = fn(1234567890);
printf("%d\r\n", nRes);
system("pause");
return 0;

}

/*
应用场景:
只能拿到函数地址但是没有办法直接调用的情况
*/

typedef int (* _math1)(int);

这段代码定义了一个函数指针类型 _math1,它指向一个接受 int 类型参数并返回 int 类型结果的函数。

  • typedef 是用来定义类型别名的关键字
  • int(* _math1)(int) 表示 _math1一个指向函数的指针,这个函数接收一个 int 类型的参数,并返回 int 类型的结果。

解释:

  1. _math1 是一个指向函数的指针
  2. 这个函数必须接受一个 int 类型的参数
  3. 函数的返回值是 int 类型

输入输出函数

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>


int main()
{
int nNumber = 0;
char * szBuffer = new char[256]{ 0 };
/*
//如果传入的是一个整型变量那么就需要传入 `&nNumber`
scanf("%d", &nNumber);
printf("%d\r\n", nNumber);

//传入的是字符串的话直接传入 `szBuffer`,因为对于字符串来说这就相当于是他的地址嘛
scanf("%s", szBuffer);
printf("%s\r\n", szBuffer);
*/

//nNumber = 0;
//memset(szBuffer, 0, 256);
//gets_s(szBuffer, 256);
//puts(szBuffer);

//int nChar = getchar();
//putchar(nChar);

//fgets(szBuffer, 256, stdin);
//fputs(szBuffer, stdout);
system("pause");
return 0;
}

结构体联合体与枚举

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

//结构体
typedef struct _SINFO {
char szName[50];
int nAge;
}SINFO,*PSINFO;
/*
PSINFO 相当于 SINFO* 相当于 struct _SINFO*
*/

//联合体 `union`关键字
typedef union _UINFO {
char szName[50];
int nAge;
}UINFO,*PUINFO;


//枚举类型
enum {
One,
Two,
Three,
Magic = 8,
Ten = 10
};

int main()
{
//结构体
SINFO sObj;
strcpy(sObj.szName, "rkvir");
sObj.nAge = 18;
printf("SINFO:%s-%d\r\n", sObj.szName, sObj.nAge);
//联合体
UINFO uObj;
strcpy(uObj.szName, "rkvir");
uObj.nAge = 18;
printf("UINFO:%s-%d\r\n", uObj.szName, uObj.nAge);

/*
会发现打印的时候前面的名字没了,只有后面的数字

*/
//枚举
printf("One:%d-Magic:%d-Ten%d\r\n", One, Magic, Ten);
system("pause");
return 0;
}

联合体和结构体的区别:

结构体的大小是所有变量的总和,所以说他是每一个变量都有自己独立的空间

联合体取得是最大变量的那个体积,其他变量都共有它

有参宏与无参宏

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

//无参宏
#define NUM 256

//有参宏
#define ADD(a,b) a + b;
#define MAX(a,b) a > b ? a:b

//判断
#define FLAG 5
#if FLAG == 1
int g_Flag = 1;
#elif FLAG == 2
int g_Flag = 2;
#else
int g_Flag = 3;
#endif

//有一些预设宏
#if _WIN32
int g_OS = 0;
#elif __linux__
int g_OS = 1;
#endif



int main()
{
printf("%d\r\n", NUM);
int nRes = ADD(NUM, 1);
printf("%d\r\n", nRes);
nRes = MAX(10, 5);
printf("%d\r\n", nRes);
if (g_OS == 0)
{
printf("Windows\r\n");
}
else
{
printf("Linux\r\n");
}
printf("Flag:%d\r\n", g_Flag);
system("pause");
return 0;
}

文件操作

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

//读文件函数 :参数需要传一个 `路径`
char * ReadFile(char * szFilePath)
{
FILE * pFile;
int nReadFileSize = 0;
if ((pFile = fopen(szFilePath, "r")) == NULL)
{
printf("open file failed!\n");
fclose(pFile);
exit(0);
}
fseek(pFile, 0, SEEK_END);
nReadFileSize = ftell(pFile);
rewind(pFile);
char * szReadBuffer = new char[nReadFileSize + 1];
if (szReadBuffer == NULL)
{
printf("malloc memory failed!\n");
fclose(pFile);
exit(0);
}
memset(szReadBuffer, 0, (nReadFileSize + 1));
int nReadRetSize = 0;
nReadRetSize = fread(szReadBuffer, 1, nReadFileSize, pFile);
if (nReadRetSize != nReadFileSize)
{
printf("Read File failed!\n");
fclose(pFile);
exit(0);
}
fclose(pFile);
return szReadBuffer;
}

//写文件 :参数需要传一个 `路径`
int WriteFile(char * szFilePath, char * szBuffer)
{
FILE * pFile;
if ((pFile = fopen(szFilePath, "w")) == NULL)
{
printf("open file failed!\n");
fclose(pFile);
exit(0);
}
int nRet = fwrite(szBuffer, strlen(szBuffer), 1, pFile);
fclose(pFile);
return nRet;
}

void xorcode(char * szBuffer, char cKey)
{
for (size_t i = 0; i < strlen(szBuffer); i++)
{
szBuffer[i] ^= cKey;
}
}
int main()
{
WriteFile((char*)"rk.ini", (char*)"This is a string!");
char * szBuffer = ReadFile((char*)"rk.ini");
printf("%s\r\n", szBuffer);
system("pause");
return 0;
}

C++类和封装

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <Windows.h>

//C++三大特性 继承 封装 多态
//类 封装
//C++ 默认 万事万物皆为对象 对象属性 和 行为

//封装的意义 属性和行为作为一个整体,表现出来生活的事务,并且将属性和行为 加以权限控制

// 公有 public 类里成员 行为 可以访问到 类外也可以访问到
// protected 类里成员 行为 可以访问到 类外不可以访问到
// private 类里成员 行为 可以访问到 类外不可以访问到
struct Role
{
void Init()
{
this->Hp = 20;
this->MaxHp = 30;
this->MaxMp = 40;
this->Mp = 50;
}

void show()
{
printf("%d %d\r\n", this->Hp, this->Mp);
}

private:
int Hp;
int Mp;
int MaxHp;
int MaxMp;
};

/*
这是c原本还没有进一步封装的内容:
void Init(Role * pRole)
{
pRole->Hp = 20;
pRole->MaxHp = 30;
pRole->MaxMp = 40;
pRole->Mp = 50;
}

void show(Role * pRole)
{
printf("初始化后内容:%d %d %d %d\r\n", pRole->Hp, pRole->MaxHp, pRole->MaxMp, pRole->Mp);
}

*/

int main()
{
Role r1;
Role r2;

r1.Init();
r1.show();

r2.Init();
//r2.Hp -= 10;
r2.show();



/*
//初始化:这是在没有封装行为的情况下
Init(&r1);
Init(&r2);
*/

/*
//printf("初始化后的r1:%d %d %d %d\r\n", r1.Hp, r1.MaxHp, r1.MaxMp, r1.Mp);
//printf("初始化后的r2:%d %d %d %d\r\n", r1.Hp, r1.MaxHp, r1.MaxMp, r1.Mp);
//封装好的打印函数
show(&r1);
show(&r2);
*/

system("pause");
return 0;
}

运算符重载

main.cpp:

这是我没有进行封装前的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

//运算符重载:对已经有的运算符进行重新定义,赋值给他另有的功能
// + - * /

struct Person
{
int a;
int b;
};

//编译器蒙了不会算了:
//大不了我们写一个返回值为Person的函数:传入两个指针类型的数据
Person PersonAddPerson(Person* p1,Person* p2)
{
Person p3;
p3.a = p1->a + p2->a;
p3.b = p1->b + p2->b;
return p3;
}
int main()
{
Person p1;
Person p2;
Person p3;
p1.a = 1;
p1.b = 2;
p2.a = 1;
p2.b = 3;

p3 = PersonAddPerson(&p1, &p2);
printf("%d and %d \r\n", p3.a, p3.b);
//p3 = p1 + p2 :编译器蒙圈了属于是
system("pause");
return 0;
}

可以看到其成功打印了:

C++是如何处理的呢?

main.cpp:c++内重载运算符号

第一种:指针类型传:Person operator+(Person * p2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

//运算符重载:对已经有的运算符进行重新定义,赋值给他另有的功能
// + - * /

struct Person
{
//我将刚刚写好的功能直接封装起来
Person PersonAddPerson(Person* p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}
//c++如何处理的呢?
/*

*/
//这指的是我们要重载这个运算符+号,这里我们可以传入 一个指针,或是一个引用
/*

Person operator+(Person *p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}

*/


Person operator+(Person * p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}


int a;
int b;

};

//编译器蒙了不会算了:
//大不了我们写一个返回值为Person的函数:传入两个指针类型的数据
Person PersonAddPerson(Person* p1,Person* p2)
{
Person p3;
p3.a = p1->a + p2->a;
p3.b = p1->b + p2->b;
return p3;
}
int main()
{
Person p1;
Person p2;
Person p3;
p1.a = 1;
p1.b = 2;
p2.a = 1;
p2.b = 3;
//p3 = p1.PersonAddPerson(&p2);
//printf("%d and %d \r\n", p3.a, p3.b);
//p3 = p1 + p2 :编译器蒙圈了属于是

/*
这里我们已经成功重载了运算符`+`号,但是因为我们写重载函数的时候传入的是一个指针类型的变量:也就是一个地址
Person operator+(Person * p2)这里我们传入的是一个指针类型,所以想要
*/
p3 = p1 + &p2;
/*
p3 = p1 + &p2; <===> p3 = p1.operator+(&p2);
*/
printf("%d and %d \r\n", p3.a, p3.b);
system("pause");
return 0;
}

第二种:引用类型传:Person operator+(Person & p2)

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

//运算符重载:对已经有的运算符进行重新定义,赋值给他另有的功能
// + - * /

struct Person
{
//我将刚刚写好的功能直接封装起来
Person PersonAddPerson(Person* p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}
//c++如何处理的呢?
/*

*/
//这指的是我们要重载这个运算符+号,这里我们可以传入 一个指针,或是一个引用

/*
Person operator+(Person *p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}

*/


//引用类型
Person operator+(Person & p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}
/*或者:值传递
Person operator+(Person p2)
{
Person p3;
p3.a = this->a + p2->a;
p3.b = this->b + p2->b;
return p3;
}
*/


int a;
int b;

};

//编译器蒙了不会算了:
//大不了我们写一个返回值为Person的函数:传入两个指针类型的数据
Person PersonAddPerson(Person* p1,Person* p2)
{
Person p3;
p3.a = p1->a + p2->a;
p3.b = p1->b + p2->b;
return p3;
}
int main()
{
Person p1;
Person p2;
Person p3;
p1.a = 1;
p1.b = 2;
p2.
//p3 = p1.PersonAddPerson(&p2);
//printf("%d and %d \r\n", p3.a, p3.b);
//p3 = p1 + p2 :编译器蒙圈了属于是

/*
这里我们已经成功重载了运算符`+`号,但是因为我们写重载函数的时候传入的是一个指针类型的变量:也就是一个地址
Person operator+(Person & p2)这里我们传入的是一个引用类型,所以想要
*/
p3 = p1 + p2;
/*
p3 = p1 + p2; <===> p3 = p1.operator+(p2);
*/
printf("%d and %d \r\n", p3.a, p3.b);
system("pause");
return 0;
}

Person& p2Person p2之间的区别:

Person& p2 —— 引用类型

  • Person& p2 表示 p2 是一个 Person 类型的引用引用在 C++ 中是一个对象的别名,它必须在定义时被初始化,并且一旦引用被绑定到某个对象,就不能再改变引用指向另一个对象。
  • 引用不会进行对象的拷贝,使用引用时访问的是原始对象。因此,引用作为参数传递时,效率通常比传递对象的拷贝要高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
示例:
#include <iostream>
using namespace std;

class Person {
public:
int age;

Person(int age) : age(age) {}

void printAge() {
cout << "Age: " << age << endl;
}
};

void modifyPerson(Person& p) {
p.age = 30; // 通过引用修改原始对象
}

int main() {
Person p1(25);
p1.printAge(); // 输出:Age: 25
modifyPerson(p1);
p1.printAge(); // 输出:Age: 30 // 修改了 p1 的年龄
return 0;
}

  • 引用传递:在 modifyPerson 函数中,p 是一个引用,因此它直接修改了 p1 对象的值。

关键点:

  • 引用 Person& p2 使得 p2 直接指向传入的对象。
  • 在函数中对 p2 的任何修改都会影响原始对象(传引用)。


Person p2 —— 对象类型(值传递)

  • Person p2 表示 p2 是一个 Person 类型的对象(而不是引用)。这意味着它是一个独立的对象,通常会在栈上分配内存。
  • 当你将 Person 类型的对象作为参数传递给函数时,通常会发生值传递,即对象会被拷贝一份传递给函数。
  • 对象的拷贝会进行成员的逐一拷贝,虽然可以通过拷贝构造函数或移动构造函数进行优化,但在大型对象或复杂结构中,拷贝操作可能会有一定的开销。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
示例:
#include <iostream>
using namespace std;

class Person {
public:
int age;

Person(int age) : age(age) {}

void printAge() {
cout << "Age: " << age << endl;
}
};

void modifyPerson(Person p) {
p.age = 30; // 仅修改 p 的拷贝,不影响原始对象
}

int main() {
Person p1(25);
p1.printAge(); // 输出:Age: 25
modifyPerson(p1); // 传递的是 p1 的拷贝
p1.printAge(); // 输出:Age: 25 // 原始对象 p1 没有被修改
return 0;
}

  • 值传递:在 modifyPerson 函数中,pp1 的拷贝,修改的是 p 的副本,p1 不会受到影响。

关键点

  • Person p2 是一个独立的对象,传递时会进行拷贝。
  • 函数中修改 p2 不会影响原始对象,除非使用指针或引用传递。

p->ap.a 的区别

在 C++ 中,p->ap.a 这两种语法虽然看起来很相似,但它们有显著的区别,主要体现在 p 的类型不同。

1. p->a —— 指针访问成员

  • p->a 是用来访问指针 p 指向的对象的成员 a
  • p 必须是一个指向对象的指针类型(Person* p)。
  • ->指针成员访问运算符,它通过指针访问对象的成员。

语法:

1
p->a   // 等价于 (*p).a
  • p->a 会先通过指针 p 解引用得到对象,然后访问该对象的成员 a
  • 如果 pnullptr 或者指向的对象不存在,那么访问 p->a 会导致运行时错误(如段错误)

2.p.a —— 对象访问成员

  • p.a 是用来访问对象 p 的成员 a

  • p 必须是一个对象,而不是指针。

  • .成员访问运算符,直接通过对象访问成员。

语法:

1
p.a
  • p.a 是直接访问对象 p 的成员 a,不需要解引用。

多态

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

/*
静态多态:函数地址早期绑定,编译阶段的时候确定函数地址
动态多态:函数地址晚绑定,在运行阶段的时候确定函数地址

有继承关系,重写父类虚函数,父类指针需要指向于子类

*/

struct Aniaml
{
public:
void Speak()
{
printf("动物在说话");
}
};

struct Cat : Aniaml
{
void Speak()
{
printf("猫在说话");
}
};

void DoSpeak(Aniaml* aniaml)//在编译阶段 就已经确定了函数地址 Aniaml (静态多态)
{
aniaml->Speak(); //Aniaml.speak已经绑定了
}

int main()
{
Cat cat;
DoSpeak(&cat);
return 0;
}

执行结果如下:显示动物在说话。

那么该如何动态多态呢?

加入关键字:virtual

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

/*
静态多态:函数地址早期绑定,编译阶段的时候确定函数地址
动态多态:函数地址晚绑定,在运行阶段的时候确定函数地址

有继承关系,重写父类虚函数,父类指针需要指向于子类 virtual

*/

struct Aniaml
{
public:
virtual void Speak()
{
printf("动物在说话");
}
};

struct Cat : Aniaml
{
void Speak()
{
printf("猫在说话");
}
};
struct Dog : Aniaml
{
void Speak()
{
printf("狗在说话");
}
};

void DoSpeak(Aniaml* aniaml)//在编译阶段 就已经确定了函数地址 Aniaml (静态多态)
{
aniaml->Speak(); //Aniaml.speak已经绑定了
}

int main()
{
Cat cat;
DoSpeak(&cat);
return 0;
}

执行结果如下:猫在说话

继承

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

/*
什么是继承:继承就是数据的复制
C++:就是让编译器 多去帮我们生成代码
共性的东西抽出来
*/

struct person
{
int age;
int sex;

void run() {};
void Study() {};
};
struct student : person
{

};
struct Woker : person
{

int salary;


void Work() {};
};

int main()
{
/*
student继承了person后内存大小会为8个字节
Worker继承了person后内存大小为12个字节
*/
printf("%d\r\n",sizeof(Woker));
return 0;
}

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

/*
什么是继承:继承就是数据的复制
C++:就是让编译器 多去帮我们生成代码
共性的东西抽出来
*/

struct A
{
public:
int a; //类内类外都可以访问
protected:
int b; //类内可以访问,类外不可以访问
private:
int c;
};

struct B : public A
{
void run()
{
this->b = 10;

}
};

int main()
{
B b1;


return 0;
}

protected属性可被继承:

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

/*
什么是继承:继承就是数据的复制
C++:就是让编译器 多去帮我们生成代码
共性的东西抽出来
*/

struct A
{
public:
int a; //类内类外都可以访问
protected:
int b; //类内可以访问,类外不可以访问
private:
int c;
};


struct B : protected A
{
/*
这个A的共有属性继承下来就变成了protected属性的
*/
void run()
{
this->a;
}
};
int main()
{
B b1;


return 0;
}

可以看到是成功继承了的:

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

/*
什么是继承:继承就是数据的复制
C++:就是让编译器 多去帮我们生成代码
共性的东西抽出来
*/

struct A
{
public:
int a; //类内类外都可以访问
protected:
int b; //类内可以访问,类外不可以访问
private:
int c;
};


struct B :private A
{
/*
这个A的共有属性继承下来就变成了private属性直接就无法访问到,只能通过类内才可以访问
*/
void run()
{
this->a;
}
};
int main()
{
B b1;


return 0;
}

异常

main.cpp:最基本的框架就如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>


int main()
{
for (int i = 0; i < 10000; i++) {
try
{
//容易发生异常的代码块
int * pNew = new int[999999];//如果发生异常就会走到catch内
}
catch (...)//代表所有异常都去接收
{
printf("发生异常\r\n");
break;
}

}
printf("main end");


return 0;
}

有一些异常用上面的基本框架是无法捕获的:比方除0异常,可以进行下面操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

int Div(int a, int b)
{
if (b == 0)
{
//throw 999;//throw抛出的异常是一个int类型
throw "发生除0异常";//throw抛出的异常是一个char*类型
//主动抛出一个异常

/*
这里抛出异常后我们并没有用try catch去处理它,它会向上传也就传到了main函数里
*/
}
return a / b;
}

int main()
{

try
{
//容易发生异常的代码块
int a = 10;
int b = 0;
int c = Div(a, b);//如果发生异常就会走到catch内
}
catch (int exc)//int exc 是接收的throw抛出的异常
{
printf("发生异常——编号:%d\r\n", exc);
}
catch (const char* exc)//const char* exc 是接收的throw抛出的异常
{
printf("发生异常——异常说明:%s\r\n", exc);
}
printf("main end");


return 0;
}

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

int Div(int a, int b)
{
try
{
if (b == 0)
{
//throw 999;//throw抛出的异常是一个int类型
throw "发生除0异常";//throw抛出的异常是一个char*类型
//主动抛出一个异常
}
}
catch(int exc)
{
printf("Div发生异常");
return -1;
}
//如果在这一层没有捕获到异常那么他就会继续向上抛,抛至main函数

return a / b;
}

int main()
{

try
{
//容易发生异常的代码块
int a = 10;
int b = 0;
int c = Div(a, b);//如果发生异常就会走到catch内
}
catch (int exc)//int exc 是接收的throw抛出的异常
{
printf("发生异常——编号:%d\r\n", exc);
}
catch (const char* exc)//const char* exc 是接收的throw抛出的异常
{
printf("发生异常——异常说明:%s\r\n", exc);
}
printf("main end");


return 0;
}

捕获如下: