学习目标:
一周掌握 C++入门知识之C语言部分
(整个学习主线是跟着黑马程序员课程:走的,博主之前学过C语言但是很长时间没接触有点忘记了,现在想系统学习C++找工作用,这篇博客主要记录自己学习过程中遇到的问题与解决办法尤其是课程中没有提到的但是对新手来说比较难理解的东西)
学习内容:
1. 搭建 C++ 开发环境
2. C++ 初识
3. 数据类型
4. 运算符
5. 程序流程结构
6. 数组
7. 函数
8. 指针
9. 结构体
10. 通讯录管理系统
学习笔记:
一、搭建 C++ 开发环境
黑马没有提到开发环境的安装,这里可以观看安装visual studio
二、C++ 初识
2.1 第一个程序
#include <iostream>
using namespace std;
int main() {
//程序员来到世界上的第一声啼哭:hello world!!!
cout << "hello world!!!" << endl;
system("pause");
return 0;
}
- #include < iostream>
作用:引入标准输入输出流库(Input/Output Stream Library)。
功能:提供 cin(控制台输入)和 cout(控制台输出)等核心功能。支持流操作符 <<(输出)和 >>(输入)。包含字符串处理(如 string 类型)等工具。 - using namespace std;
作用:声明使用标准命名空间 std。
功能:允许直接使用 std 命名空间内的标识符(如 cout 代替 std::cout)。简化代码书写,避免重复添加 std:: 前缀。 - main()函数是程序入口点
核心作用:main 函数是 C++ 程序的唯一入口点,程序从这里开始执行。
编译器要求:每个 C++ 程序必须包含且只能有一个 main 函数。
常见疑问
Q1:能否省略 return 0;?
• 可以:C++ 标准允许 main 函数隐式返回 0。
Q2:能否修改 main 的名称?
• 不能:编译器仅识别 main 作为入口函数,其他名称(如 main2)会导致编译错误。
Q3:返回非 0 值的意义?
• 错误码:返回非 0(如 return 1;)表示程序异常退出,可供脚本或父进程判断执行结果。 - system(“pause”);
功能:调用操作系统的命令行工具执行 pause 命令(仅限 Windows 系统)。在 Windows 的命令提示符(CMD)中,pause 会暂停程序执行,并显示提示文字(如“按任意键继续…”),直到用户按下任意键。
目的:防止控制台窗口在程序结束后立即关闭,方便用户查看输出结果。 - return 0;
功能:表示程序正常退出,并向操作系统返回状态码 0。在 main 函数中,return 0; 是可选的(C++ 标准规定未显式返回时默认为 0)。非零返回值通常表示程序异常终止(如错误码 1)。
意义:明确告知操作系统程序已成功执行完毕,便于脚本或其他程序根据返回值判断执行结果。
2.2 注释
#include <iostream>
using namespace std;
int main() {
//程序员来到世界上的第一声啼哭:hello world!!!
//这是单行注释
/*
这是多行注释
这是多行注释
这是多行注释
*/
cout << "hello world!!!" << endl;
system("pause");
return 0;
}
2.3 变量与常量
顾名思义变量就是可以改变的而常量不可改变,变量又分为整型、浮点型、字符型等等,每个变量都有一个存储空间用于存放数据,我们通过变量名称就可以调用或修改这个数据例如int a = 1;中变量名为a,数据为1;
常量常见定义方式有两种在C++中,宏常量(通过#define定义)和常量(通过const或constexpr定义)有本质区别,主要体现在语法、类型安全、作用域和编译过程等方面。以下是它们的核心差异:
宏常量 | 常量 |
---|---|
通过预处理器指令#define定义,例如:#define PI 3.14159 | 通过const或constexpr定义,例如:const double PI = 3.14159; |
预处理阶段处理:在编译前进行简单的文本替换(无类型检查)。 | 编译阶段处理:作为真正的变量,有类型和作用域。 |
2.4 关键字
C++ 关键字是语言预定义的保留字,具有特定语法和语义功能,不可用作变量名、函数名或类名。以下是 C++ 关键字的分类及核心作用:
2.4.1基础关键字
- 数据类型与修饰符
关键字 | 作用 |
---|---|
int | 定义整型变量 |
float, double | 单精度/双精度浮点数 |
char | 字符类型 |
bool | 布尔类型(true/false) |
void | 表示“无类型”(用于函数无返回值或通用指针) |
signed/unsigned | 有符号/无符号修饰符(如 unsigned int) |
short/long | 短整型/长整型(如 long long) |
- 控制结构
关键字 | 作用 |
---|---|
if, else | 条件分支 |
switch/case/default | 多分支选择 |
for, while, do | 循环语句 |
break, continue | 跳出循环或跳过当前迭代 |
goto | 无条件跳转(慎用,易导致代码混乱) |
- 存储类与作用域
关键字 | 作用 |
---|---|
auto | 自动推导变量类型(C++11 后多用于简化复杂类型声明) |
register | 建议编译器将变量存入寄存器(C++17 弃用) |
static | 静态变量(延长生命周期)或静态成员(类内共享) |
extern | 声明外部变量或函数(跨文件访问) |
mutable | 允许类的常量成员被修改(如配合 const 方法) |
thread_local | 线程局部存储(C++11) |
2.4.2 面向对象编程
- 类与继承
关键字 | 作用 |
---|---|
class | 定义类(默认成员为 private) |
struct | 定义结构体(默认成员为 public) |
union | 定义联合体(共享内存的不同类型成员) |
enum | 定义枚举类型 |
virtual | 虚函数(实现多态) |
override | 显式标记重写基类虚函数(C++11,增强可读性) |
final | 禁止派生类重写虚函数或继承类(C++11) |
- 访问控制
关键字 | 作用 |
---|---|
public | 公有成员(任何地方可访问) |
private | 私有成员(仅类内和友元可访问) |
protected | 受保护成员(类内和派生类可访问) |
2.4.3 内存管理
关键字 | 作用 |
---|---|
new | 动态分配内存(返回指针) |
delete | 释放动态内存 |
sizeof | 获取类型或对象的内存大小 |
this | 指向当前对象的指针 |
2.4.4 其他重要关键字
关键字 | 作用 |
---|---|
const | 常量修饰符(变量不可修改)或常量成员函数(不修改对象状态) |
constexpr | 编译时常量表达式(C++11,优化性能) |
volatile | 告知编译器变量可能被意外修改(如硬件寄存器) |
typedef | 定义类型别名(C++11 后可用 using 替代) |
namespace | 定义命名空间(避免命名冲突) |
template | 定义模板(泛型编程) |
typename | 指明模板中的依赖类型名(C++11) |
co_await/co_yield/co_return | 协程支持(C++20) |
2.4.5 版本相关关键字
版本 | 新增关键字 | 作用示例 |
---|---|---|
C++11 | auto(类型推导)、nullptr | auto x = 5;(x 类型为 int) |
C++11 | constexpr、override、final | constexpr int f() { return 42; } |
C++17 | if constexpr、inline(变量) | if constexpr (condition) {…} |
C++20 | concept、requires、char8_t | template concept Integral = … |
2.4.6 标识符命名规则
作用:C++规定给标识符(变量、常量)命名时,有一套自己的规则
标识符不能是关键字
标识符只能由字母、数字、下划线组成
第一个字符必须为字母或下划线
标识符中字母区分大小写
建议:给标识符命名时,争取做到见名知意的效果,方便自己和他人的阅读
三、数据类型
3.1 整型
类型的取值范围是根据占用空间计算的,比如short型占用2字节,每个字节有8位,每一位有0和1两种状态,所以2字节共占16位,去除第一位表示正负(1代表负,0代表正),剩余的15位用于表示数字,采用二进制计算,15位可表示的最大数字就是2^15-1,因为负数在计算机中是以补码的形式存放的,所以把特殊情况:当符号位为1其余位为0时直接代表最大的负数- 2 ^15,其余情况均可通过减一再取反还原。
3.2 实型(浮点型)
有效数字是指小数点前后数字的个数之和。
当输入float型数据超过有效位数时会自动四舍五入,如下:
#include <iostream>
using namespace std;
int main() {
//程序员来到世界上的第一声啼哭:hello world!!!
float c = 3.123123123123;
cout << c <<"所占空间是" << sizeof(c) << endl;
system("pause");
return 0;
}
运行结果如下:
科学计数法:
3.1e2 代表3.110^2 = 310
3.1e-2代表3.10.1^2 = 0.031
3.3 字符型和字符串型
C语言风格的字符串:
char 变量名[] = "abc" ;
C++风格的字符串:
#include <string>
string 变量名 = “abc” ;
3.4 转义字符
主要记住下面三个红框中的就可以
3.5 布尔类型
bool类型之占一个字节,有两个值数字1代表“真”,数字0代表“假”。
四、运算符
4.1 算术运算符
需要注意的是:双目运算符(+、-、*、/)中的结果的类型有两种情况,
一是当参与运算的变量中没有实型时,结果的类型为变量中所占内存最大的类型,比如
short num1=1; int numb2 = 2; cout << numb1+numb2<<endl;
结果类型为int型
二是当参与运算的变量中有实型时,结果的类型都为double类型,比如
long long num1=1; float numb2 = 2.0; cout << numb1+numb2<<endl;
结果类型为double型
4.2 赋值运算符
4.3 比较运算符
4.4 逻辑运算符
注意:与和或均是有中断性质,例如a&&b,若a为假则已经可以得出结果为假,此时b不再执行。同理a||b中,若a为真也不再执行b.
4.5 逗号运算符和逗号表达式
逗号表达式的一般形式为:
表达式1,表达式2,表达式3,……,表达式n
它是值为表达式n的值。
例如:a = 3 * 5,a*4 结果为60。
五、程序流程结构
5.1 选择结构
if语句
在多条件语句中if和else遵循就近匹配原则,例如
if(a==1)
if(b==2)b++;
else c++;
在上面这段代码中,else与第二行的if匹配,若a不是1则后面两行代码均不会执行。
三目运算符
表达式1 ?表达式2 :表达式3
int a = 1;int b = 2;
( a > b ? a : b ) = 100;
结果b=100。
switch语句
5.2 循环结构
while循环语句
do while循环语句
for循环语句
5.3 跳转语句
break
跳出一层循环体,即提前结束循环
continue
结束本次循环
goto
六、数组
6.1 一维数组的定义方式:
类型名 数组名[常量表达式];
例如定义一个长度为3的数组:
int arr1[3];//第一种方式,直接定义数组
const int n = 3;int arr2[n];//第二种方式,用常量定义数组,不可用变量定义
int arr3[] = {1, 2, 3};//第三种方式,初始化定义数组
一维数组的引用方式:
数组名 [下标]
int arr3[] = {1, 2, 3};
cout<<arr3[0]<<arr3[1]<<arr3[2]<<endl;//结果为123
一维数组在内存中是连续的,计算数组元素个数可以通过数组长度除以单个数据长度获得:
int n = sizeof(arr3)/sizeof(arr3[0]);
6.2 二维数组定义方式:
类型名 数组名[常量表达式][常量表达式];
例如定义一个三行四列数组:
int array1[3][4];
int array2[3][4] = { {1, 2},{3, 4} };
int array3[3][4] = {1, 2, 3, 4};
int array4[][4] = {{1,2,3,4},{5,6,7,8},{9,10};
二维数组的引用:
数组名 [下标][下标];
二维数组在内存中同样是连续的,计算二维数组元素个数可用整个数组长度除以单个元素长度:
int n = sizeof(array3)/sizeof(array3[0][0]);
计算行数可用数组长度除以一行的长度:
int row = sizeof(array3)/sizeof(array3[0]);
计算列数可用数组一行的长度除以单个元素的长度:
int line = sizeof(array3[0])/sizeof(array3[0][0]);
七、函数
7.1 函数的定义
无参数形式:
类型名 函数名([void])
{声明部分
执行语句
}
有参数形式:
类型名 函数名([形式参数表列])
{声明部分
执行语句
}
7.2 函数的调用
函数名 [参数];
7.3 函数的重载
C++允许用同一函数名定义多个函数,且重载函数的参数个数、参数类型或参数顺序三者中至少有一种不同。如下:
#include<iostream>
using namespace std;
int max(int numb1, int numb2) {
if (numb1 >= numb2) {
return numb1;
}
return numb2;
}
float max(float numb1, int numb2) {
if (numb1 >= numb2) {
return numb1;
}
return numb2;
}
float max(float numb1, float numb2) {
if (numb1 >= numb2) {
return numb1;
}
return numb2;
}
int main() {
int num1 = 1;
int num2 = 2;
float num3 = 4.5;
float num4 = 3.2;
int numb5 = max(num1, num2);
float numb6 = max(num3, num1);
float numb7 = max(num3, num4);
cout << "numb5 = " << numb5 << endl;
cout << "numb6 = " << numb6 << endl;
cout << "numb7 = " << numb7 << endl;
system("pause");
return 0;
}
运行结果:
函数的分文件编写
比如把上面的重载函数max另写一个文件中,先在头文件夹中创建max.h,在源文件中创建max.cpp,目录结构如下:
然后在max.h中写入函数声明:
#include<iostream>
using namespace std;
int max(int numb1, int numb2);
float max(float numb1, int numb2);
float max(float numb1, float numb2);
在max.cpp中写入函数定义:
#include"max.h"//指明头文件防止出错
int max(int numb1, int numb2) {
if (numb1 >= numb2) {
return numb1;
}
return numb2;
}
float max(float numb1, int numb2) {
if (numb1 >= numb2) {
return numb1;
}
return numb2;
}
float max(float numb1, float numb2) {
if (numb1 >= numb2) {
return numb1;
}
return numb2;
}
在主函数调用max函数时只需加上头文件即可:
#include<iostream>
using namespace std;
#include"max.h"
int main() {
int num1 = 1;
int num2 = 2;
float num3 = 4.5;
float num4 = 3.2;
int numb5 = max(num1, num2);
float numb6 = max(num3, num1);
float numb7 = max(num3, num4);
cout << "numb5 = " << numb5 << endl;
cout << "numb6 = " << numb6 << endl;
cout << "numb7 = " << numb7 << endl;
system("pause");
return 0;
}
八、指针
8.1 指针的定义、调用和所占内存空间
变量的指针就是变量的地址,用来存放变量地址的变量就是指针变量。
作用:可以通过指针间接访问内存。
指针定义形式:
基类型 * 指针变量名;
例如:float a = 3; float * p = &a;
与指针变量有关的两个运算符:
(1)&:取地址运算符;
(2) * :指针运算符(或称间接访问运算符)
例如:&a为变量a的地址,*p为指针变量p所指向的存储单元。
指针的调用:
直接用 * 运算符加指针变量名调用即可。
例如:float a = 3; float * p = &a; cout<<*p<<endl;
结果为a的值3。
注:32位系统下每个指针所占内存大小均为4字节,而64位系统下指针占8字节,与类型无关。
8.2 空指针和野指针
野指针:指针变量指向非法的地址空间。比如:未申请的内存地址空间。
注:尽可能的避免程序中出现空指针和野指针,防止程序出现未知错误。
8.3 const修饰指针
(1)const修饰指针:常量指针,指针的指向可以改,指针指向的内容不可以改。
形式:const 基类型 * 指针变量名;
例如:
int a = 3;
int b = 4;
const int * p = &a;
p = &b;//可以,修改指针的指向;
*p = 4;//不可以,修改指针指向的内容;
(2)const修饰常量:指针常量,指针的指向不可以改,指针指向的内容可以改。
形式:基类型 * const 指针变量名;
例如:
int a = 3;
int b = 4;
int * const p = &a;
p = &b;//不可以,修改指针的指向;
*p = 4;//可以,修改指针指向的内容;
(3)const即修饰指针又修饰常量:指针的指向不可以改,指针指向的内容也不可以改。
形式:const 基类型 * const 指针变量名;
例如:
int a = 3;
int b = 4;
const int * const p = &a;
p = &b;//不可以,修改指针的指向;
*p = 4;//不可以,修改指针指向的内容;
8.4 数组与指针
用指针访问数组元素
例如
int a[10];
int * p = a;//与int * p = &a[0];等价
p++;
cout<<*p<<endl;//输出a[1]
注:*p++
等价于 先*p
后p++
;
*++p
等价于 先++p
后*p
;
++*p
等价于 ++(*p)
;
二维数组
指向二维数组的指针:
(1)把二维数组展平为一维数组:指向i行j列地址(注:i,j均从0开始):*(a+i)+j, &a[i][j], a[i]+j
计算方式(第i行j列元素,m代表最大列数):i*m+j
例如:
int a[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
int* p = a;//错误
int** p = a;//错误
int* p1 = &a[0][0];//正确,相当于把二维数组展平为一维数组
int* p2 = *(a+1)+2;//正确,指向第1行第2列地址
cout<<a[1][2]<<"="<<p1[6]<<endl;//输出第1行第2个元素值,1*4+2=6,结果:6=6
(2)指向二维数组的行地址,引用时需指明列数:指向第i行地址:a+i, &a[i]
调用方式:指向i行j列元素:a[i][j], *(*(a+i)+j), *(a[i]+j)
例如:
int a[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
int* p = a;//错误
int** p = a;//错误
int (*p1)[4] = a;//正确,因为[]优先级比*大,所以要用(*p),指向第0行首地址
int (*p2)[4] = &a[1];//正确,指向第1行首地址
cout<<a[1][2]<<"="<<p1[1][2]<<"="<< *(*(p1+1)+2)<<"="<<endl;//输出结果:6=6=6
8.5 指针与函数
值传递:不会改变实参
地址传递:会改变实参
例如;
#include<iostream>
using namespace std;
void swap1(int a, int b) {
int temp = a;
a = b;
b = temp;
}
void swap(int* a, int* b) {
int temp = * a;
*a = *b;
*b = temp;
}
int main() {
int b = 4;
int c = 5;
swap1(b, c);//值传递
cout << "b=" << b << " c=" << c << endl;
swap(&b, &c);//地址传递
cout << "b=" << b << " c=" << c << endl;
system("pause");
return 0;
}
运行结果:
九、结构体
9.1 结构体的定义和使用
#include<iostream>
using namespace std;
#include<string>
struct Student {
string name;
int age;
}s3 = {"大黄",22};
int main() {
Student s1 = { "小明",18 };
Student s2;
s2.name = "小红";
s2.age = 20;
cout << s1.name << s1.age << endl;
cout << s2.name << s2.age << endl;
cout << s3.name << s3.age << endl;
system("pause");
return 0;
}
输出结果:
9.2 结构体数组
#include<iostream>
using namespace std;
#include<string>
struct Student {
string name;
int age;
}s[3] = {"大黄",22,"小明",18,"小红",20};
int main() {
s[1].name = "张三";
s[2].age = 30;
for (int i = 0;i < 3;i++) {
cout << s[i].name << s[i].age << endl;
}
system("pause");
return 0;
}
运行结果:
9.3 结构体指针
struct 结构体名 * 指针名;
结构体指针中(*p).name等价于p->name
#include<iostream>
using namespace std;
#include<string>
struct Student {
string name;
int age;
}s[3] = {"大黄",22,"小明",18,"小红",20};
int main() {
struct Student* stu = s;
stu[2].name = "张三";
stu[2].age = 30;
for (int i = 0;i < 3;i++) {
cout << stu->name << stu->age << endl;
stu++;
}
system("pause");
return 0;
}
9.4 结构体嵌套
在学生结构体里嵌套一个成绩结构体
#include<iostream>
using namespace std;
#include<string>
struct Score {
string subject;
int score;
};
struct Student {
string name;
int age;
Score sco[2];
}s[3] = { {"大黄",22,"语文",60,"数学",50},
{"小明",18,"语文",40,"数学",30},
{"小红",20,"语文",90,"数学",100} };
int main() {
struct Student* stu = s;
stu[2].name = "张三";
stu[2].age = 30;
stu[0].sco[0] = { "语文",70 };
stu[0].sco[1] = { "数学",80 };
for (int i = 0;i < 3;i++) {
cout << stu->name << stu->age << endl;
cout << stu->sco[0].subject << stu->sco[0].score<<" ";
cout << stu->sco[1].subject << stu->sco[1].score << endl;
stu++;
}
system("pause");
return 0;
}
运行结果:
9.5 结构体做函数参数
1、值传递
2、地址传递
跟前面函数传参作用一样,区别就是把数据类型改为结构体了,这里不再展示。
9.6 结构体中const使用场景
将函数中的形参改为指针可以减少内存空间,而且不会复制新的副本出来。
但是为了保护地址传递时原数据不被改变,可以在形参前加const,相当于常量指针。
例如:
print_(const struct Student *stu){...}//相当于常量指针
十、通讯录管理系统
自己看一遍视频后,手敲了一遍代码,为了让系统更稳定根据自己的理解做了一点小改动;代码放在文章顶部了,感兴趣的同学可以自己做一遍。