参考网址:C++基础入门
几个理解的知识点:
1.变量,变量类型,变量名的作用
答:
变量:是用来存放各种数据类型的容器,是一段内存空间。程序中定义了一个变量,就等于程序向内存条申请了一段存储空间。
变量类型:是为了指定多大的内存空间分配给变量,如整型,浮点型,字符型,字符串型,布尔型等。不同变量类型的变量所占用的内存大小是不一样的。
变量名:是给一段内存空间命名,这样就不需要内存空间的物理地址,直接通过变量名,访问内存空间存储的数据,简化操作。
常量:是变量的特殊形式,只是内存空间中的值不能修改。
2.数据类型存在的意义
答:
给变量分配合适的内存空间,如整型,浮点型,字符型,字符串型,布尔型等数据类型的变量所占用的内存空间是不同的。
其中字符型变量、字符串型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元。而布尔类型,true存储的是1,false存储的是0。
3.数据类型和数据结构
答:
数据类型:简单数据类型有整型,浮点型,字符型,字符串型,布尔型;复杂数据类型有结构体、类
数据结构:变量是最简单的一种数据存储结构;数组也是一种数据结构;线性表、树、队列等都是数据结构。容器如vector、list、deque、set、map等都是各种数据结构,用来存放数据。
注:
数据结构,强调数据之间的关系,代表一种数据的存储结构,如树,队列,列表,字典等。
数据类型,强调合法的操作,如对字符串,数字可以进行什么样的操作。
4.cin和cout
答:
cin相当于C语言中的scanf函数,用于用户输入数据;
cout相当于C语言中的print函数,用于打印输出数据;
cin >> a; //表示用户输入的数字,存储到变量a中。
cout << "当前输入为:" << a << endl; //表示打印a的值。
5.代码注释
单行注释:// 描述信息 (通常放在一行代码的上方,或者一条语句的末尾,对该行代码解释)
多行注释: /* 描述信息 */ (通常放在一段代码的上方,对该段代码做整体解释)
从七个方面理解C++基本语法的知识体系:
数据类型、运算符、程序流程控制、数组、函数、指针、结构体
一、数据类型
整型:short、int(4)、long、long long
答:
整型所占内存空间大小,因操作系统的种类和位数不同而有所区别。
注意:
硬件的64位和32位,指的是CPU位宽,即CPU可以处理的数据比特数。
软件的64位和32位,指的是程序指令的位宽,包括操作系统的位数,也是我们经常提到的32位和64位含义。
浮点型:float(4)、double(8)
字符型:char(1)
字符串型:字符数组、字符指针、string类
布尔型:bool(1)
二、运算符
算术运算符、赋值运算符、比较运算符、逻辑运算符
三、程序流程控制(逻辑控制)
1.顺序结构
从程序入口开始,程序一步一步顺序执行。
2.选择结构
1)if语句
if
if —else
if—else if—else
嵌套if(多层if语句)
2)三目运算符
c = (a > b ? a : b)
三目运算符返回的是变量,可以继续赋值。
3)switch语句
switch(表达式)
{
case 结果1:执行语句;break;
case 结果2:执行语句;break;
...
default:执行语句;break;
}
注意1:switch语句中表达式类型只能是整型或者字符型
注意2:case里如果没有break,那么程序会一直向下执行
总结:与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间,即case后面的结果只能是整型或字符型。
3.循环结构
1)while循环:
while(循环条件)
{
循环语句
}
注意:
当写成while(1)或者while(true)时,会陷入死循环,程序将一直循环执行。
当写成while(num)时,num如果为0,表示假,条件不成立,退出此循环;当num不为0,表示真,会一直循环执行。
可以使用break语句退出当前循环,包括可以退出死循环。
2)do … while循环:
do
{
循环语句
} while(循环条件);
注意:与while循环的区别在于,do…while会先执行一次循环语句,再判断循环条件。
3)for循环:
for(起始表达式;条件表达式;末尾循环体)
{
循环语句;
}
注意:
起始表达式只执行一次;
执行顺序是先判断条件表达式,再执行循环语句,最后执行末尾循环体,再判断条件表达式…
如:
for (int i = 0; i < 10; i++)//这里i++和++i没有区别
{
cout << i << endl;
}
4)嵌套循环:
外层循环执行一次,内层循环执行一周。
4.补充
1) break语句:用于跳出选择结构或者循环结构
break使用的时机:
出现在switch条件语句中,作用是终止case并跳出switch
出现在循环语句中,作用是跳出当前的循环语句
出现在嵌套循环中,跳出最近的内层循环语句
2)continue语句:
用在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。
注意:continue并没有使整个循环终止,而break会跳出循
3)goto语句:可以无条件跳转语句
使用语法: goto 标记;
(如果标记的名称存在,执行到goto语句时,会跳转到标记的位置
四、数组
所谓数组,就是一个集合,里面存放了相同类型的数据元素。
(数组是一种特殊的容器,用于存储数据。数组(Array)是一种线性表数据结构)
特点1:数组中的每个数据元素都是相同的数据类型
特点2:数组是由连续的内存位置组成的
特点3:数组名是给一段连续的内存空间起个名字,数组中的元素通过索引得到。
特点4:数组名是一个常量,不可以再次修改。数组中的元素是变量,可以进行修改。
特点5:每一次程序运行时,都会重新给程序分配内存空间,从而每次内存地址都是不同的。一个字节为一个地址;占多个字节的变量,用首字节的地址表示。
注:数组可以直接作为函数参数,也可以用数组名(地址)作为函数参数
1.一维数组名称的用途
sizeof(array):可以统计整个数组在内存中的长度,即统计数组占据多少个字节空间。
sizeof(array[0]):查看数组中第一个元素的内存所占大小
cout << array:可以获取数组在内存中的首地址
cout << &array[0]:查看数组的第一个元素的物理地址
注意:冒泡排序法
对9个数字进行冒泡排序:
2.二维数组名称的用途
查看二维数组所占内存空间,
获取二维数组首地址。
注意:二维数组名就是这个数组的首地址,二维数组的某一行构成一维数组,可以直接得到某一行的首地址。对于一个具体的数组元素,需要用&取地址符号。
//二维数组数组名
int arr[2][3] ={{1,2,3},{4,5,6}};
cout << "二维数组大小: " << sizeof(arr) << endl;
cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
//地址
cout << "二维数组首地址:" << arr << endl;
cout << "二维数组第一行地址:" << arr[0] << endl;
cout << "二维数组第二行地址:" << arr[1] << endl;
cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
五、函数
作用:将一段经常使用的代码封装起来,减少重复代码
1.函数定义
2.函数调用
使用已经定义好的函数,调用函数时,实参会传递给形参。(值传递时,如果形参发生改变,并不会影响实参)
使用语法:函数名(实参)
内存解释过程:
函数调用过程中,实参a,b自始至终没有发生变化,形参num1,num2发生了变换,但形参变化不影响实参。
3.函数声明
有了函数声明,被调用函数可以定义在主函数后面,不需要一定放在前面。
作用: 告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
函数的声明可以多次,但是函数的定义只能有一次
4.函数的分文件编写
让代码结构更加清晰
函数分文件编写一般有4个步骤:
1)创建后缀名为.h的头文件
2)创建后缀名为.cpp的源文件
3)在头文件中写函数的声明
4)在源文件中写函数的定义
5)关联.h头文件和.cpp源文件
6)引用.h头文件到当前程序即可。
六、指针
1.指针的定义和使用
1)指针就是地址,保存着变量a表示的内存空间的地址编号。
注:指针和指针变量的区别
“指针”是概念,指针就是地址,地址就是指针。(地址就是内存单元的编号)
“指针变量”是具体实现,指针变量我们一般简称指针,它是一个变量,所以需要进行定义。指针变量是用来存放另一个变量的地址(即指针),显然,指针变量的值改变了,那么其指向的值就变了。
例如:a是整型变量,值为10;
p为指针变量,值为变量a表示的内存空间的地址,所以p=&a。
由于p也是一块内存空间,同样有地址编号,即&p;
*p表示以p内存空间的内容为地址,寻址得到p地址内存空间保存的内容,即 *p=a。
(指针变量定义语法: 数据类型 * 变量名;
这里面的“数据类型”表示指针指向的内容,它的数据类型。指向的值是整型数字,这里就是int;指向的值是字符,就是char;指向的值是实数,这里就是float或者为double)
2)指针变量和普通变量的区别
定义变量等于申请一块内存空间,一块内存空间有名字(变量名),也有编号(地址),也有存储的内容(数据)。
普通变量存放的是数据,指针变量存放的是地址
指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
总结1: 我们可以通过 & 符号 获取变量的地址
总结2:利用指针可以记录地址
总结3:对指针变量解引用" * ",可以操作指针指向的内存。
如果不是指针变量,就不能对其进行 " * "操作,所以我们必须先定义一个指针变量,即int * p;表示定义了一个变量,其是指针变量,后面可以使用 解引用操作,并且这个指针变量的数据类型是整型,即p的内容为整型。
3.指针所占内存空间
提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?
4.空指针和野指针
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
野指针:指针变量指向非法的内存空间
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
5.const修饰指针
作用:是限定指针的指向和指针指向的内容,指针的指向是指针变量p保存的内容,即地址;指针指向的内容是以地址(指针的指向)来寻址得到的内容。普通指针可以修改指针的指向和指针指向的内容,而const修饰的指针,不能随意修改这两个东西。
const修饰指针有三种情况:
const修饰指针 — 常量指针,即某个常量的指针,表示指针指向的值是个常量,即指针指向的内容不可以更改,但指针本身值可以更改
const修饰常量 — 指针常量,表示这个指针是个常量,即指针本身值不可以更改,但指针指向的内容可以更改
const即修饰指针,又修饰常量 — 常量指针 + 指针常量
1)const修饰指针 — 常量指针
答:
就是某个常量的指针,表示指针指向的值是个常量,即指针指向的内容不可以更改,但指针本身值可以更改
const int * p = &a; const限制了 *,带*操作不能被直接修改,如*p=20
常量指针表示*p是一个常量了,即指针p指向的内容是一个常量,不能修改,但p本身是个变量,可以修改。
重点:虽然不能直接修改* p,但通过改变p的值,* p自然就改变了
//1.常量指针
void test01()
{
int a = 10;
int b = 10;
int c = 20;
const int *p = &a;
cout << "指针p的值:" << p << endl;
cout << "指针p指向的值:" << *p << endl;
//更改指针p的值,但指针p指向的值不发生改变
p = &b; //指针p的值变了,表示其指向的内存地址发生了变化,但是内存中的值没有发生变化。(指针p的值原来是0x0011内存,现在是0x0022内存,但两块内存中存储的值是相同的)
cout << "指针p的值:" << p << endl;
cout << "指针p指向的值:" << *p << endl;
//更改指针p指向的值,但指针p的值不发生改变
//*p = 10; //不允许修改,指针p指向的值是常量
//更改指针p的值,同时更改指针p指向的值
p = &c;
cout << "指针p的值:" << p << endl;
cout << "指针p指向的值:" << *p << endl;
}
2)const修饰常量 — 指针常量
答:
表示这个指针是个常量,即指针本身值不可以更改,但指针指向的内容可以更改
在变量p前,加const进行修饰,从而p变成常量;
所以const修饰常量这句话表示是,加入const后,原来的变量p变成常量了,注意此时p还不认为是指针类型的变量,因为还没加int*.
又当因为前面有int*,表示的是指针变量,从而叫指针常量。
int * const p = &a,即const p看成常量。const限制了p,p不允许直接修改,如p = &b
指针常量,很显然,p是一个常量了,那么p的内容是不允许修改的,但指针常量p所指向的值是可以修改的,就地址不变,但地址所对应的数据可以变。(这个也是可以理解的,变量a的地址不能变,但a的值可以变)
重点:
虽然不能直接修改p的值,那能不能通过修改p指向的值,使得p的值也自然就改变了呢?
注意,这是错误的,通过修改p指向的值,不会改变p本身的值,它不像常量指针那样,可以通过更改指针的值,达到更改“指针指向的值”这个目的。根本原因是指针是因,指针指向的值是果,不能通过改变结果来影响原因,但可以通过更改原因来产生不同的结果
//指针常量
void test02()
{
int a = 10;
int b = 10;
int c = 20;
int * const p = &a;
cout << "指针p的值:" << p << endl;
cout << "指针p指向的值:" << *p << endl;
//更改指针p指向的值,但指针p的值不发生改变
*p = c;
cout << "指针p的值:" << p << endl;
cout << "指针p指向的值:" << *p << endl;
//更改指针p的值,但指针p指向的值不发生变化
//p = &b; //不允许修改,指针p的值是一个常量
}
3)const即修饰指针,又修饰常量
答:
就是常量指针+指针常量。
在指针int*前,加入const修饰;在变量p前,也加入const修饰。表示这个指针的指向,和这个指针指向的内容,都被固定死,都不允许修改了。
//常量指针+指针常量
void test03()
{
int a = 10;
int b = 10;
int c = 20;
const int * const p = &a;
cout << "指针p的值:" << p << endl;
cout << "指针p指向的值:" << *p << endl;
//更改指针p的值,但指针p指向的值不发生改变
//p = &b;//不允许修改,指针p的值是一个常量
//更改指针p指向的值,但指针p的值不发生变化
//*p = c;//不允许修改,指针p指向的值是常量
}
6.指针和数组
作用:利用指针访问数组中元素
7. 指针和函数
作用:利用指针作函数形参,可以修改实参的值
(下图内存条的演示中,p1和p2应该为p1和p2)
总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
8 指针、数组、函数
就是通过指针,将数组作为函数中的参数,进行传递。因为函数中的参数通常为一个单值,而数组为一连串值,此时需要地址来传递,因而用到指针。
七、结构体
一种特殊的数据类型,它的使用类似于整型、字符型等数据类型,但不像int、char等数据类型是C++自带的,结构体需要我们自己定义,所以需要将复杂概念,简单理解。
创建一个结构体变量,理解就是创建一个变量,数据类型是结构体。
创建一个结构体数组,理解就是创建一个数组,数据类型是结构体。
创建一个结构体指针,理解就是创建一个指针,数据类型是结构体,因为指向的值的数据类型是结构体。
总结:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型,所以结构体是一种数据类型。
1.结构体定义和使用
定义一个结构体,就是在定义一种新的数据类型。
语法:struct 结构体名 { 结构体成员列表 };
通过结构体创建变量的方式有三种:创建一个结构体变量,就是在创建一个变量,它的数据类型是结构体。
1)struct 结构体名 变量名
struct Student s1;类似于int a,只不过这里变量s1的数据类型是结构体。
2)struct 结构体名 变量名 = { 成员1值 , 成员2值…}
3)定义结构体时顺便创建变量:一般不使用
总结1:定义结构体时的关键字是struct,不可省略
总结2:创建结构体变量时,关键字struct可以省略
(区别:结构体是一种数据类型,结构体变量是一种变量,数据类型是结构体。)
总结3:结构体变量利用操作符 ‘’.’’ 访问成员
2.结构体数组
数组中的元素都是结构体类型
语法:struct 结构体名 数组名[元素个数] = { {} , {} , … {} }
3.结构体指针
通过指针访问结构体中的成员
利用操作符 ->可以通过结构体指针访问结构体属性
下图中,指针的数据类型是结构体,因为其“指向的值”的数据类型是结构体。好比“指向的值”的数据类型是整型,那么指针的数据类型也应该是整型。
4.结构体嵌套结构体
作用: 结构体中的成员可以是另一个结构体
结构体嵌套结构体:定义和使用
5.结构体做函数参数
将结构体作为参数向函数中传递
传递方式有两种:值传递,地址传递
理解就是将结构体变量作为函数参数,如同可以将一个整型变量作为一个函数参数,同样分为值传递和地址传递。(具体原理参考函数章节)
1)先定义结构体,再定义一个结构体变量。
2)值传递时,将结构体变量作为函数参数。
3)地址传递时,将结构体指针作为函数参数,其中这个结构体指针指向的值为结构体变量。
注:如果不想修改主函数中的数据(实参),用值传递,反之用地址传递。
示例:
//学生结构体定义
struct student
{
//成员列表
string name; //姓名
int age; //年龄
int score; //分数
};
//值传递
void printStudent(student stu )
{
stu.age = 28;
cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
}
//地址传递
void printStudent2(student *stu)
{
stu->age = 28;
cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age << " 分数:" << stu->score << endl;
}
int main() {
student stu = { "张三",18,100};
//值传递
printStudent(stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
cout << endl;
//地址传递
printStudent2(&stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
system("pause");
return 0;
}
6.结构体中 const使用场景
作用:用const来防止误操作
原理:值传递时,结构体变量作为函数参数,需要实参数据全部复制到函数中,内存占用大。
而地址传递时,结构体指针作为函数参数,传入的只是一个地址,内存占用小;但是,地址传递时,子函数变化可能会导致主函数中的实参数据变化;为了不让此种情况发生,引入const,来约束子函数的形参,使其为常量结构体指针,从而不能修改结构体指针指向的值。(常量指针,不允许修改指针指向的值)