C++基础
C++基础
VS快捷键
Ctrl +或- 跳转到上次鼠标焦点位置
Ctrl K F 按住Ctrl 然后按K 然后按F
Ctrl J 代码提示
变量
声明方式:数据类型 变量名 = 变量初始值
#include <iostream>
using namespace std;
int main()
{
int a = 1;
char b = 'b';
}
常量
#define 宏常量
声明方式:#define 常量名 常量值
通常在文件上方定义
#include <iostream>
using namespace std;
#define day 7
int main()
{
cout << "一周有" << day <<"天" << endl;
}
const修饰的变量
声明方式:const 数据类型 变量名 = 变量值
#include <iostream>
using namespace std;
int main()
{
const int day = 7;
cout << "一周有" << day <<"天" << endl;
}
命名规则
- 标识符不能是关键字
- 标识符只能有字母,数字,下划线
- 第一个字符必须为字母或下划线
- 区分大小写
数据类型
整型
- short 2字节
- int 4字节
- long windows 4字节 Linux32位 4字节 Linux64位 8字节
- long long 8字节
sizeof关键字
- 用来获取数据类型所占用内存大小
- 语法: sizeof( 数据类型 / 变量 )
#include <iostream>
using namespace std;
int main()
{
int a = 1;
long b = 1;
long long c = 1;
cout << "int类型占用:" << sizeof(int) << endl;
cout << "int变量占用:" << sizeof(a) << endl;
cout << "long类型占用:" << sizeof(long) << endl;
cout << "long变量占用:" << sizeof(b) << endl;
cout << "long long类型占用:" << sizeof(long long) << endl;
cout << "long long变量占用:" << sizeof(c) << endl;
}
输出结果(我使用的是windows 所以long类型也是4字节大小):
int类型占用:4
int变量占用:4
long类型占用:4
long变量占用:4
long long类型占用:8
long long变量占用:8
浮点型
- double 8字节 有效数字范围15-16位
- float 4字节 有效数字范围7位
需要注意的一点是:当我们声明一个float类型的带小数的变量时,编辑器会默认认为是double类型,准确指出类型需要在变量值最后加F
#include <iostream>
using namespace std;
int main()
{
float a = 1.1F;
float b = 1.1;
}
上面两种都可
字符型
语法: char c='a'
- 字符类型只能使用单引号,而不是双引号
- 只能是单个字符,不能是字符串
- c/c++中字符类型只占用
1字节
- 字符类型并不是把字符本身放到内存中存储,而是转换为对应的ASCll码存放
#include <iostream>
using namespace std;
int main()
{
char a = 'h';
char b = 'e';
cout << "变量a:" << a << endl;
cout << "变量b:" << b << endl;
}
输出char类型变量的ascll码
强转为int类型输出即可
#include <iostream>
using namespace std;
int main()
{
char a = 'h';
char b = 'e';
cout << "变量a:" << (int)a << endl;
cout << "变量b:" << (int)b << endl;
}
转义字符
就写几个常用的
- \n 换号
- \t 一个table的位置
- \\ 代表一个\
字符串类型
C风格字符串
声明方式: char 变量名[] = "字符串值"
#include <iostream>
using namespace std;
int main()
{
char s[] = "hello world";
cout << s << endl;
}
C++风格字符串
声明方式:string 变量名="字符串值"
如果使用不了string 需要导入头文件
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "hello world";
cout << s << endl;
}
Bool类型
表示真和假,只有两个值
- false 0
- true 1
bool类型只占用一个字节大小
#include <iostream>
#include <string>
using namespace std;
int main()
{
bool b = true;
cout << b << endl;
}
数据的输入
用于获取键盘的输入
关键词:cin
语法: cin>>变量
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 1;
cin >> a;
cout << a << endl;
}
运算符
- 算数运算符 处理四则运算
- 赋值运算符 将表达式赋值给变量
- 比较运算符 表达式的比较并返回一个bool值
- 逻辑运算符 用于根据表达式的值返回true或false
算数运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -3 | -3 |
+ | 加号 | 10+5 | 15 |
- | 减号 | 10-5 | 5 |
* | 乘号 | 10*5 | 50 |
/ | 除号 | 10/5 | 2 |
% | 取模(取余) | 10%3 | 1 |
++ | 前置自增 | a=2;b=++a; | a=3;b=3; |
++ | 后置自增 | a=2;b=a++; | a=3;b=2; |
-- | 前置自减 | a=2;b=--a; | a=1;b=1; |
-- | 后置自减 | a=2;b=a--; | a=1,b=2 |
tips: 两数相除,除数不能为0
两数取余,第二个数也不能为0;
两个小数是不能做取余操作
int main()
{
int a = 2;
int b = a++;
//其他操作也基本类似,就不粘代码了
cout << a << endl;
cout << b << endl;
}
赋值运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
= | 赋值 | a=2; | a=2; |
+= | 加等于 | a=2; a+=1; | a=3; |
-= | 减等于 | a=2;a-=1; | a=1; |
*= | 乘等于 | a=2;a*=2; | a=4; |
/= | 除等于 | a=4;a/=2; | a=2; |
%= | 模等于 | a=3;a%=2; | a=1; |
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 1;
a += 1;
// a = a + 1; 等同于这段代码 其他操作类似
cout << a << endl;
}
比较运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
== | 相等于 | 1==1 | 1 (true) |
!= | 不等于 | 1!=1 | 0 (false) |
< | 小于 | 1<1 | 0 (false) |
<= | 小于等于 | 1<=1 | 1 (true) |
> | 大于 | 1>1 | 0 (false) |
>= | 大于等于 | 1>=1 | 1 (true) |
#include <iostream>
#include <string>
using namespace std;
int main()
{
cout << (1<=1) << endl;
}
逻辑运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
! | 非(取反) | !a | 如果a为假,则!a为真 反之亦然 |
&& | 与 (并且) | a&&b | 如果ab都为真,则结果为真,其中一个为假,则整体为假 |
|| | 或 (或者) | a||b | 只要其中一个为真,则结果为真,都为假则结果为假 |
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 10;
cout << !a << endl;
/*
结果为0 因为逻辑运算符会把变量当成bool类型来操作,bool中只有0和非0
那么10作为非0的数值被当成true,取反为false, 而false的int类型数值为0
所以结果为0
*/
cout << !!a << endl;
/*
结果为1 原因如上,当第一个!走完后结果已经为0了,而bool值只有0和非0
非零默认值是1,所以两次取反后结果为1
*/
//加几个!没有限制,可以无限加
}
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 10;
int b = 0;
cout << (a && b) << endl;
/*
结果为0 (false)
*/
}
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 10;
int b = 0;
cout << (a|| b) << endl;
/*
结果为1 (true)
*/
}
程序流程结构
选择结构
if
#include <iostream>
#include <string>
using namespace std;
int main(){
int a = 10;
int b = 0;
if (a) {
cout << "a" << endl;
}
else if (b){
cout <<"b" << endl;
}
else {
cout << "else" << endl;
}
}
三目运算符
语法:条件?为true返回的:否则返回的
例如a=1;b=2;
a>b?a:b
a大于b吗?是的返回a,否则返回b
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 10;
int b = 0;
int c = a > b ? a : b;
cout << c << endl;
/*
结果10
*/
}
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 10;
int b = 0;
//返回变量并赋值
( a > b ? a : b)=50;
cout << a << endl;
cout << b << endl;
/*
结果a=50
b=0;
*/
}
不仅可以返回变量,也可以在三目中进行赋值操作等
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 10;
int b = 0;
int c;
a > b ? c = 10 : c = 20;
cout << c << endl;
//结果c=10;
}
switch
#include <iostream>
#include <string>
using namespace std;
int main() {
char a = 's';
switch (a)
{
case 's':
cout << "s" << endl;
break;
case 'h':
cout << "h" << endl;
break;
default:
cout << "default" << endl;
break;
}
//结果 s
}
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 3;
switch (a)
{
case 1:
cout << "1" << endl;
break;
case 2:
cout << "2" << endl;
break;
default:
cout << "default" << endl;
break;
}
//结果 default
}
tips : 如果一个case没有break,则会继续执行下面的case直到结束 default也会执行
循环结构
while
语法:while(条件){循环语句}
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 1;
while (a < 10) {
cout << ++a << endl;
}
//一直循环到10结束
}
do..while
语法: do{循环语句} while(条件)
#include <iostream>
#include <string>
using namespace std;
int main() {
bool eat = false;
do {
cout << "吃个汉堡,还要吃吗?"<< endl;
cin >> eat;
} while (eat);
/*
结果:
吃个汉堡,还要吃吗?
1
吃个汉堡,还要吃吗?
1
吃个汉堡,还要吃吗?
0
程序结束
*/
}
for
语法:for(起始变量;循环条件;循环结束执行代码){循环语句}
#include <iostream>
#include <string>
using namespace std;
int main() {
char my_name[] = "jame";
for (int i = 0; i < sizeof(my_name); i++)
{
cout << my_name[i] << endl;
}
/*
sizeof可以获取到占用的内存大小,就是char的个数 一个char占一字节 那么sizeof(my_name)结果就是4
每次循环输出具体下标的内容就可以完成遍历了
结果:
j
a
m
e
*/
}
小练习:打印乘法表
#include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= i; j++)
{
cout << i << "*" << j << "=" << (i*j) << " ";
}
cout << "\n";
}
}
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
跳转语句
语法: break
使用的时机:
- 在switch中,终止case并跳出switch
- 循环中跳出当前循环
上面的乘法表中使用
#include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= i; j++)
{
if (j == i)
{
break;
}
cout << i << "*" << j << "=" << (i*j) << " ";
}
cout << "\n";
}
}
2*1=2
3*1=3 3*2=6
4*1=4 4*2=8 4*3=12
5*1=5 5*2=10 5*3=15 5*4=20
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72
当i==j的时候直接跳出循环了,所以看不到1乘1,2乘2这样类似的输出
contiune
当在循环中出现contiune时,直接进行下次循环
#include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 1; i <= 9; i++)
{
if (i % 2 == 0)
{
continue;
}
cout << i;
}
}
//结果133579
goto
无条件跳转
语法:goto 标记
如果标记的位置存在,则执行到goto时会直接跳转到标记的位置
#include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 1; i <= 9; i++)
{
if (i == 8)
{
goto END;
}
cout << i;
}
END:
cout << "结束啦";
}
不光是往下面跳,还能往上跳
#include <iostream>
#include <string>
using namespace std;
int main() {
int b = 0;
RESTART:
if (b > 0) {
cout << "b!" << endl;
}
for (int i = 1; i <= 9; i++)
{
if (i == 8 && b == 0)
{
b = 10;
goto RESTART;
}
cout << i ;
}
}
/*
结果
1234567b!
123456789
*/
使用goto向上面跳时一定要注意结束的判断,避免死循环
数组
特点1:同个数组内的每个元素都相同类型的
特点2:数组是内存中一块连续内存地址组成的
定义数组
int main() {
int a[10];
int b[10] = { 1,2,3 }; //如果标记长度为10,但是只初始化3个值,那么剩下的默认值都是0
int c[] = { 1,2,3 };
}
三种定义数组的方式
访问数组
int main() {
int a[10];
a[1] = 1;
a[2] = 100;
}
数组名用途
- 可以获取整个数组在内存中的长度
- 可以获取内存中数组的首地址
#include <iostream>
#include <string>
using namespace std;
int main() {
int a[10] = { 1,2,3 };
cout << "数组占用总大小" << sizeof(a) << endl;
cout << "每个元素占用大小" << sizeof(a[0]) << endl;
cout << "数组中有多少个元素" << sizeof(a)/sizeof(a[0]) << endl;
}
数组占用总大小40
每个元素占用大小4
数组中有多少个元素10
tips: 数组中有多少个元素 这里使用a[0] 而不是其他a[1],a[2]...的原因 首先数组的特性1,每个元素都是相同类型的,也就是所有的元素大小都是一样的,使用那个都可以
那么也可以使用a[1],a[2]... 但是需要注意的是下标不能超过定义的最大长度
int main() {
int a[10] = { 1,2,3 };
cout << "数组首地址"<< a << endl;
cout << "数组第一个元素的首地址"<< &a[0] << endl;
}
tips: &在变量前是获取元素的地址
二维数组
定义:
#include <iostream>
#include <string>
using namespace std;
int main() {
int a[3][3]; //声明一个三行三列的数组
int b[3][3] = { {1,2,3},{4,5,6},{7,8,9} }; //声明三行三列,同时赋初始值
int c[3][3] = { 1,2,3,4,5,6,7,8,9 }; //声明三行三列 同时全部赋值 三个为一行
int d[][3] = { 1,2,3,4,5,6 }; //声明一个两行三列 同时赋值
}
数组名称
#include <iostream>
#include <string>
using namespace std;
int main() {
int b[3][3] = { {1,2,3},{4,5,6},{7,8,9} }; //声明三行三列,同时赋初始值
cout << "二维数组首地址:" << b << endl;
cout << "二维数组一行大小:" << sizeof(b[0]) << endl;
cout << "二维数组元素大小:" << sizeof(b[0][0]) << endl;
}
二维数组遍历
#include <iostream>
#include <string>
using namespace std;
int main() {
int b[3][3] = { {1,2,3},{4,5,6},{7,8,9} }; //声明三行三列,同时赋初始值
for (int i = 0; i < (sizeof(b)/sizeof(b[0])); i++)
{
for (int j = 0; j < (sizeof(b[0])/sizeof(b[0][0])); j++)
{
cout << b[i][j];
}
}
}
函数(方法)
一个函数包含以下内容:
- 返回值类型
- 函数名称
- 参数列表
- 函数体
- return
//返回值类型和函数名称,参数为空
int main() {
//函数体
cout << "hello world";
//return值
return 0;
}
函数的调用
int sum(int number1, int number2) {
return number1 + number2;
}
int main() {
cout << sum(1,2);
return 0;
}
tips: C++和JAVA不一样,被调用的函数必须在调用函数的方法前声明
值传递
值传递是指当实参传递给形参后,当形参发生改变,实参并不会发生变化
int test(int number1) {
number1 += 1;
return 0 ;
}
int main() {
int a = 1;
test(a);
cout << a;
return 0;
}
当调用完test函数后a的值输出还是1,或者我们输出number1
和a
的地址,也会发现不同,说明test的参数只是值传递
int test(int number1)
{
number1 += 1;
cout << &number1 << endl;
return 0 ;
}
int main()
{
int a = 1;
test(a);
cout << &a<<endl;
return 0;
}
/*
000000FF976FF550
000000FF976FF574
*/
函数常见样式
-
有参有返
int sum(int a,int b) { return a+b; }
-
有参无返
void print_number(int number) { cout << number; }
-
无参有返
int get_one() { return 1; }
-
无参无返
void print_hello(){ cout << "hello"; }
函数的声明
在函数的开始篇有个tips 说使用的被调用的函数必须声明在调用的函数前,而函数声明就可以让被调用的函数声明在调用的函数之后
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 1;
print(a);
return 0;
}
void print(int a) {
cout << a;
}
我们这么写编译是没有问题,但是当运行起来后就会报错:print找不到标识符
调换个位置就可以正常运行
void print(int a) {
cout << a;
}
int main() {
int a = 1;
print(a);
return 0;
}
而函数的声明就是提前声明我有这个函数
语法:返回类型 函数名(参数);
一个函数可以被声明多次,但是只能定义一次
#include <iostream>
#include <string>
using namespace std;
void print(int a); //提前声明存在print函数
int main() {
int a = 1;
print(a);
return 0;
}
void print(int a) {
cout << a;
}
函数分文件编写
将相同功能的函数单独放在一个文件中,使结构更加清晰
- 创建后缀为
.h
的头文件 - 创建后缀为
.cpp
的源文件 - 在头文件中写函数声明
- 在源文件中写函数实现
myHead.h
#include <iostream>
using namespace std;
void print(int a);
myHead.cpp
#include "myHead.h";
void print(int a) {
cout << a;
}
主文件
#include "myHead.h";
int main() {
int a = 1;
print(a);
return 0;
}
我们在.h头文件中引入了iostream
也使用了std
那么只要导入myHead.h
文件的都可以使用
咋说呢,有点像java的接口?哈哈哈
指针
指针的作用:可以通过指针直接访问内存
- 内存编号从0开始,一般为16进制数字表示
- 可以使用
指针变量
保存地址
指针的定义和使用
语法:数据类型* 变量名
int main() {
int a = 1;
int* a_pointer = &a;//将a的地址存放到指针中
*a_pointer = 100;//将100赋值给指针指向的地址中 指针前加*代表解引用 找到指针指向内存中的数据
cout << a << endl;
cout << "a的地址" << &a<<endl;
cout << "a指针的数据"<<a_pointer << endl;
}
指针占用的内存
int main() {
cout << sizeof(int *) << endl;
cout << sizeof(short *) << endl;
cout << sizeof(float *) << endl;
cout << sizeof(double *) << endl;
cout << sizeof(long *) << endl;
cout << sizeof(long long *) << endl;
}
32位中都是4字节 64位中都是8字节
空指针和野指针
空指针:指针变量指向内存中编号为0的空间
用途: 初始化指针
注意: 空指针的内存是不可访问的
int main() {
int* a = NULL;
cout << *a << endl;
//*a = 100; 或者这段代码
}
//都会报一个访问权限的异常
野指针:指针变量指向非法的内存空间
int main() {
int* a = (int*)0x10000; //(int * 强制转换为指针类型)
*a = 100;
}
//也会报一个访问权限的异常
tips: 空指针和野指针都不是我们申请的空间,因此不要访问
const修饰指针
const有三种情况
- 修饰指针:常量指针
- 修饰常量:指针常量
- 即修饰指针,又修饰常量
常量指针
int main() {
int a = 10;
int b = 20;
/*
* 常量指针
* 指针指向的值不可修改,但是可以修改指向
*/
const int* p = &a;
//*p = 10; 修改指向的值 错误
p = &b;//修改指向 可以
}
指针常量
int main() {
int a = 10;
int b = 20;
/*
* 指针常量
* 指针指向的值不可修改,但是可以修改指向
*/
int* const p = &a;
*p = 10; //修改指向的值 可以
//p = &b;//修改指向 错误
}
即修饰指针,又修饰常量
int main() {
int a = 10;
int b = 20;
/*
* 即修饰指针,也修饰常量
* 指针指向的值不可修改,但是可以修改指向
*/
const int* const p = &a;
//*p = 10; //修改指向的值 错误
//p = &b;//修改指向 错误
}
指针和数组
利用指针访数组中的元素
int main() {
int a[5] = { 1,2,3,4,5 };
int* p = a;
cout << *p;
p++;
cout << *p;
}
tips: 指针类型++ 加的是sizeof(指针类型) 的值, int加4字节正好是下个元素的地址 然后*p解引用取值也就是下个元素的值
指针和函数
利用指针作为函数的参数,从而修改实参的值,也就是常说的值传递和引用传递,而当使用指针作为参数传递时,就是引用传递
void add(int* num1) {
(*num1)++;
}
int main() {
int a = 10;
add(&a);
cout << a;
}
当执行完后a的值为11
tips: 括号标明是先去获取指针指向的值,然后自增1,如果不加可能以为是指针值++然后取值
指针数组和函数
小练习 冒泡
int* arr接收数组的地址,可以当做数组使用 解释:C++ primer plus 90page
void golugolu(int* arr ,int lenght)
{
for (int i = 0; i < lenght; i++)
{
for (int j = 0; j < lenght - i- 1; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j]=arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int a[5] = { 2,3,1,4,5 };
golugolu(a,5);
for (int i = 0; i < 5; i++)
{
cout << a[i];
}
}
结构体
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
语法: struct 结构体名 {结构体成员列表}
;
tpis:
注意! 结构体结束}后面需要加一个;结尾
通过结构体创建变量的方式有三种:
- struct 结构体名 变量名
- struct 结构体名 变量名=
- 定义结构体时同时设置变量
方式1
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
};
int main()
{
struct Student student; //struct可以省略
student.age = 10;
student.name = "张三";
student.score = 90;
cout << student.age << endl;
}
方式2
#include <iostream>
#include <string> //如果string类型报错则导入该头文件
using namespace std;
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
};
int main()
{
struct Student student = { "张三",90,18 }; //struct可以省略
cout << student.name << endl;
}
类似于有参构造,每个变量的顺序就是在结构体中声明的顺序
方式3
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
} oldStudnet; //标明一个结构体
int main()
{
//可以使用上面两种来进行填充数据
oldStudnet = { "we",1,1 };
oldStudnet.age = 199;
cout << oldStudnet.name << endl;
cout << oldStudnet.age << endl;
}
结构体数组
语法:struct 结构体名 数组名[个数]={{},{},{},...{}
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
};
int main()
{
struct Student arr[3] =
{
{"张三",1,1},
{"李四",2,2},
{"王五",3,3}
};
//整个数组长度除以每个元素长度获取到多少个元素
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
//在循环中就可以将arr[i]当成具体的struct
arr[i].age = 10;
cout << arr[i].name;
}
}
结构体指针
语法: 可以通过->
来访问结构体中的属性
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
};
int main()
{
//创建结构体变量
struct Student studnet = { "张三",1,1 };
//创建指针变量,同时指向student变量
struct Student* p = &studnet;
//使用-> 操作符操作变量age
p->age = 100;
cout << p->age;
}
结构体嵌套结构体
struct Dog {
string name;
int age;
};
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
struct Dog dog;
};
int main()
{
struct Dog dog = { "旺财",4 };
//将dog设置到student中
struct Student student = { "小明",90,10,dog };
//先获取学生的指针
struct Student* student_p = &student;
//之后通过指针在获取到学生内部的狗的地址赋值给狗指针
struct Dog* dog_p = &student_p->dog;
//通过狗指针获取狗age
cout << dog_p->age;
}
结构体做函数参数
有两种:值传递和引用传递
struct Dog {
string name;
int age;
};
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
struct Dog dog;
};
//值传递
void set_value(struct Student s1) {
s1.age = 100;
s1.dog.age = 10;
}
//引用传递
void set_value2(struct Student* s1) {
//设置学生年龄
s1->age = 100;
//设置狗的年龄
s1->dog.age = 10;
//这种也可以,通过指针操作
//struct Dog* dog_p = &(s1->dog);
//dog_p->age = 10;
}
int main()
{
struct Dog dog = { "旺财",4 };
struct Student student = { "小明",90,18,dog };
set_value2(&student);
cout << student.dog.age << endl;
cout << student.age << endl;
}
结构体中使用const
struct Dog {
string name;
int age;
};
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
struct Dog dog;
};
struct Dog dog = { "旺财",4 };
void print(const struct Student* s) {
struct Student student = { "老明",90,18,dog };
s = &student;
//s->name = "2";报错
cout << s->name;
}
int main()
{
struct Student student = { "小明",90,18,dog };
print(&student);
}
tpis: 形参接收struct 例如 void print(const struct Student* s)
你这个Student拼写错了是不会报错的,需要注意拼写,或者直接省略
struct
关键字
//定义结构体
struct Student
{
//声明结构体中的成员
string name;
int score;
int age;
struct Dog dog;
};
struct Dog dog = { "旺财",4 };
void print(struct Student* const s) {
struct Student student = { "老明",90,18,dog };
//s = &student; 报错
s->name = "王哈哈";
cout << s->name;
}
int main()
{
struct Student student = { "小明",90,18,dog };
print(&student);
}
结构体const和修饰普通的指针一样
const struct Student* s
形参里面的值不能改变 但是指向的地址可以改变
struct Student* const s
形参里面值可以改变,但是指向不能改变
const struct Student* const s
两个都不能变
new
语法:new 数据类型
使用new创建的数据会返回对应类型的指针
int* create_int() {
return new int(10);
}
int main()
{
int * a=create_int();
cout << *a;
}
delete 释放数据
int* create_int() {
return new int(10);
}
int main()
{
int * a=create_int();
cout << *a;
delete a;
cout << *a; //异常
}
new创建数组
int* create_int() {
return new int[5] {1, 2, 3, 4, 5};
}
int main()
{
int* a = create_int();
cout << a[1];
delete[] a; //释放数组需要加一个[]
cout << a[1]; //异常
}
引用
语法:数据类型 &别名=原名
给变量起别名
int a=10;
int& b=a;
b=20;
//等价与
int a=10;
int* b=&a;
*b=20;
简化一下指针
int main()
{
int a = 10;
int& b = a;
b = 20;
//int* p = &b;
//*p = 20;
cout << a;
}
- 引用在声明时必须初始化
- 引用在初始化后不能修改
int main()
{
int a = 10;
int& b = a;
int c = 20;
b = c;//这个是修改值操作,而不是修改b引用c
b = 15;
cout << a;
cout << c; //c还是20
}
引用做函数参数
函数传参时可以利用引用让形参修饰实参
优点 :可以简化指针修改实参
//值传递
void set_value0(int a) {
a = 100;
}
//指针传递(引用
void set_value1(int* a) {
*a = 100;
}
//引用传递
void set_value2(int& a) {
a = 100;
}
int main()
{
int a=0;
//set_value0(a);
//set_value1(&a);
set_value2(a);
cout << a;
}
引用做函数返回值
注意!不要将局部变量的引用返回
局部变量数据存放在栈中,当方法结束出栈,数据也会失效
int& get() {
static int a = 10; //不加static视为局部变量,出栈后就失去了对数据的操作权限
return a;
}
int main()
{
int& a = get();
cout << a;
}
当一个方法返回类型为一个引用时,可以作为左值
int& get() {
static int a = 10;
return a;
}
int main()
{
int& a=get();
cout << a;
get() = 20;
cout << a;
}
因为这个方法返回的是一个引用,我们可以把它当成一个自己定义的引用变量来操作,输出的是一个10 一个20
引用本质
void get(int& a) {
a = 100;
}
int main()
{
int a = 20;
get(a);
cout << a;
}
void get(int* const a) {
*a = 100;
}
int main()
{
int a = 20;
get(&a);
cout << a;
}
上面两段代码意义是相同的,引用本质是指针常量,这也说明了为什么引用不能再次修改
常量引用
int main()
{
const int& a = 100;
}
当去掉const后就会报错,因为100是一个常量,不能直接赋值给引用a,除非这个引用是一个常量引用
void get(const int& a) {
a = 100;
}
int main()
{
int a = 100;
get(a);
}
上面这段会报错,因为已经使用const修饰了int& a 也就是说在get方法中a只能进行读值操作,即修改不了值,也修改不了指向
常量引用好像和值传递没有啥区别?不是的,常量引用仅仅指向地址,本身只是一个指针占4字节,如果当入参数据比较大的情况下,可以节省不少内存
函数提高
函数的默认值
在c++中函数的形参是可以指定默认值的
语法:返回值类型 函数名(参数=默认值){}
int sum(int a , int b=20, int c = 20) {
return a + b + c;
}
int main()
{
int a = 100;
cout<< sum(a);
}
有默认值的参数必须靠后面,没有默认值的参数必须在最前面
函数声明如果存在默认值,函数实现就不能有默认参数
int sum(int a, int b = 10);
int sum(int a, int b = 20) {
return a+b;
}
int main()
{
int a = 100;
cout<< sum(a);
}
这么写编译不会出错,当运行时就会发生错误
函数占位参数
语法:返回值类型 函数名(数据类型)
int sum(int a, int) {
return a;
}
int main()
{
int a = 100;
cout<< sum(a,10);
}
啊这。。。占位符还能有默认值
int sum(int a, int = 10) {
return a;
}
int main()
{
int a = 100;
cout << sum(a, 10);
}
函数重载
满足条件:
- 同个作用域下
- 函数名称相同
- 函数的参数类型不同,个数不同或顺序不同
注意:函数的返回值类型不能作为重载的条件
void a() {}
void a(int) {}
void a(int, int) {}
void a(char) {}
int main()
{
a();
a(1);
a(1, 1);
a('a');
}
引用作为重载条件
void fun(const int & a) //有const
{
cout << a;
}
int main()
{
int a = 10;
fun(a);
}
void fun(int & a) //没有const
{
cout << a;
}
int main()
{
int a = 10;
fun(a);
}
上面这个两段都可以执行,但是当同时存在时,使用变量作为参数的会调用没加const的,而直接输入一个数字作为参数的会调用带const的
void fun(int & a)
{
cout << a << "not_const" << endl;
}
void fun(const int& a)
{
cout << a<< "const" << endl;
}
int main()
{
int a = 10;
fun(a);
fun(100);
}
函数重载和默认值同时存在
void fun(int a)
{
cout << a << "not_const" << endl;
}
void fun(int a,int b=10)
{
cout << a<< "const" << endl;
}
int main()
{
fun(10); //会报错
}
void fun(int a)
{
cout << a << "not_const" << endl;
}
void fun(int a,int b=10)
{
cout << a<< "const" << endl;
}
int main()
{
fun(10,10); //正常执行
}
类和对象
封装
- 将行为和属性作为一个整体
- 将属性的行为加以权限控制
创建一个类
语法:class 类名{访问权限:属性/行为};
class Student {
public:
//属性
string name;
public: //可以使用一个public: 下面写属性和方法 分开比较容易看
//行为
void say_hello()
{
cout << "hello!my name is:"<< name<< endl;
}
void set_name(string v_name)
{
name = v_name;
}
};
int main()
{
Student s;
s.set_name("小明");
s.say_hello();
}
权限
public 公开
protected 保护
private 私有的
和java一样
class Student {
public:
string name;
protected:
int age;
private:
string password;
};
struct和class区别
唯一的就是默认访问权限不同
struct默认是public
class默认是private
对象的初始化和清理
构造函数和析构函数
构造函数语法:类名(){}
- 构造函数没有返回值也不写void
- 函数名称和类名相同
- 构造函数可以存在参数,因此可以发生重载
- 程序在初始化对象时会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名(){}
- 析构函数没有返回值也不写void
- 函数名与类名相同,前面加上~
- 析构函数没有参数,因此不可能发生重载
- 程序在销毁对象时会调用析构函数,无需手动调用,而且只会调用一次
class Student {
public:
Student() {
cout << "init";
}
~Student() {
cout << "delete";
}
};
int main()
{
Student s;
}
构造函数的分类和调用
按参数分类:有参和无参
按类型分类:普通和拷贝
三种调用方式:
括号法,显示法,隐式转换法
class Student {
public:
int age;
public:
//无参构造-默认构造
Student() {
cout << "init";
}
//有参构造
Student(int a) {
age = a;
}
//拷贝构造
Student(const Student& s) {
age = s.age;
}
~Student() {
cout << "delete";
}
};
int main()
{
//调用默认构造
Student s;
//Student s(); 不要这么写,编译器以为你在声明方法,而不是去调用无参构造,c++允许方法中声明方法的
//括号法调用有参构造
Student s1(1);
//括号法调用拷贝构造
Student s2(s1);
//显示法
Student s3 = Student(1);
Student(10);//一个匿名对象,走完这行就回收
Student(s3); //不要使用拷贝构造来初始化一个匿名对象
//隐士转换-默认调用(int a)的有参构造
Student s4 = 10;
Student s5=s3//拷贝构造
}
拷贝构造函数调用时机
- 使用一个已经创建完的对象来初始化一个新对象
- 值传递的方式给函数传值
- 以值的方式返回局部变量
实体类:
class People {
public:
People() {
cout << "无参构造" << endl;
}
~People() {
cout << "析构函数" << endl;
}
People(int _age) {
age = _age;
cout << "有参构造" << endl;
}
People(const People& p) {
age = p.age;
cout << "拷贝构造" << endl;
}
private:
int age;
};
第一种 使用一个已经创建完的对象来初始化一个新对象
int main() {
People p1(1);
People p2(p1);
}
有参构造
拷贝构造
析构函数
析构函数
第二种 值传递的方式给函数传值
void test(People p) {
}
int main() {
People p1(1);
test(p1);
}
有参构造
拷贝构造
析构函数
析构函数
第三种 以值方式返回局部变量对象
People test() {
People p1(1);
return p1;
}
int main() {
test();
}
构造函数调用规则
默认情况下,c艹编译器会给一个类添加三个构造
- 默认构造,无参,函数体为空
- 默认析构函数,无参,函数体为空
- 默认拷贝构造函数,对属性值进行拷贝
如果自定义了构造函数,c++不会提供构造函数,但是会提供默认拷贝构造
如果自定义了拷贝函数,c++不会提供其他构造
类似java
深拷贝和浅拷贝
浅拷贝:简单的赋值操作
深拷贝:在内存中重新申请空间,进行拷贝操作
class People {
public:
People() {
cout << "无参构造" << endl;
}
~People() {
cout << "析构函数" << endl;
}
People(int _age) {
age = _age;
cout << "有参构造" << endl;
}
private:
int age;
};
int main() {
People p1(1);
People p2(p1);
}
没有写拷贝函数,调用的是编译器生成的,是浅拷贝
class People {
public:
People() {
cout << "无参构造" << endl;
}
~People() {
//当对象销毁时,清空堆中的数据
if (money != NULL) {
delete money;
money = NULL;
}
cout << "析构函数" << endl;
}
People(int _age, int _money) {
money = new int(_money);
age = _age;
cout << "有参构造" << endl;
}
private:
int age;
int* money;
};
int main() {
People p1(1,20);
People p2(p1);
}
上面的代码会报错
class People {
public:
People() {
cout << "无参构造" << endl;
}
//这么写就可以
~People() {
if (money != NULL) {
if (*money != NULL) {
delete money;
*money = NULL;
}
money = NULL;
}
cout << "析构函数" << endl;
}
People(int _age, int _money) {
money = new int(_money);
age = _age;
cout << "有参构造" << endl;
}
private:
int age;
int* money;
};
int main() {
People p1(1,20);
People p2(p1);
}
原因:当p2执行完后,因为是栈,先进后出,先执行p2的析构方法,而因为是浅拷贝p1和p2的money指针都指向堆区同一块地址,当if(money!=NULL) 判断的是指针是否为空,而不是指针指向的地址是否为空,p2指针不为空那么进入if,释放堆区内存,之后置空p2对象的指针,然后p1执行析构方法,因为它俩是两个对象,p1的指针也不为空,也进入if进行清空堆区的操作,那么就会出错,因为p1指向的堆区内存已经被释放过了,
或者我们使用深拷贝,重新创建一块地址存放money
class People {
public:
People() {
cout << "无参构造" << endl;
}
~People() {
//if还是使用第一次报错的那个
if (money != NULL) {
delete money;
*money = NULL;
money = NULL;
}
cout << "析构函数" << endl;
}
People(int _age, int _money) {
money = new int(_money);
age = _age;
cout << "有参构造" << endl;
}
//自定义拷贝构造
People(const People& p) {
age = p.age;
money = new int(*p.money);
}
private:
int age;
int* money;
};
int main() {
People p1(1, 20);
People p2(p1);
}
初始化列表
语法:构造函数():属性值1(值1),属性值2(值2)..{}
class People {
public:
int age;
int hight;
/*People() {
cout << "无参构造" << endl;
}*/
~People() {
cout << "析构函数" << endl;
}
//无参构造声明默认值
People() :age(1), hight(20) {
}
//有参构造,同时赋值给对应的属性
People(int _age, int _hight) :age(_age), hight(_hight) {
}
};
int main() {
People p1(2, 55);
People p;
cout << p.age << endl;
cout << p1.age << endl;
}
类对象作为类的成员
class B {
public:
~B() {
cout<<"b析构" << endl;
}
};
class A {
public:
B& b;
A(B& _b):b(_b) { //这里接收没有加&
cout << &_b << endl;
cout << &b << endl;
}
~A() {
cout << "A析构" << endl;
}
};
int main() {
B b;
A a(b);
}
我遇到一个错误,b析构走两次,也就是说创建两个b,后来发现是引用接收的参数没有加&
创建顺序是先创建内部的,之后创建外部的,销毁顺序相反,先销毁外层的,再销毁内部的,类似栈的逻辑就能捋顺了,先进后出
静态成员
在成员变量或函数上添加static
关键字
静态成员变量:
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数:
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
tips: 同时需要注意,静态的成员变量和函数也都是可以被权限修饰的
class A {
public:
//定义一个静态的成员变量
static int number;
};
//设置初始值
int A::number = 1;
int main() {
A a;
//通过对象访问静态变量
cout << a.number;
//也可以通过类名::属性进行访问
cout << A::number;
}
所有的A类型的对象都共享一份数据,一个对象为A类型的对象修改了number的值,所有为A类型的对象中number属性的值都会变化
class A {
public:
static int number;
int age;
static void print() {
cout << number;
cout << age; //错误因为访问的并不是一个静态的成员变量
}
};
class A {
public:
static int number;
int age;
static void print() {
cout << number;
}
};
int A::number = 1;
int main() {
A a;
a.print();
A::print();
}
和静态的成员变量一样,两种访问方式,通过类名::方法或对象.方法
对象的模型和this指针
成员变量和成员函数分开存储
在c++中成员变量和成员函数分开存储,只有静态成员才属于类的对象上
一个空的对象,即里面没有属性和方法,那么它占用一个字节的大小,用来区分对象的位置
非静态的成员变量属于类的对象上,例如A类中添加一个int类型的非静态成员变量,那么A的大小就是4,不需要在加一字节来区分了
而静态的成员变量和静态函数/非静态函数都不属于类的对象上
也就是说能对对象大小进行影响的只有非静态的成员变量
this
因为非静态的成员函数只会创建一个函数实例,也就是说多个对象会共用一个方法,this指针就是来区分当前是那个对象
this指针指向被调用的成员函数所属的对象
- this指针隐含在每一个非静态的成员函数内
- this指针不需要定义,可以直接使用
- 当形参和成员变量相同时,使用this指针来区分
- 在类的非静态成员函数中返回对象本身,可以使用 return *this;
class A {
public:
int age;
A(int age) {
//标明那个是形参,那个是属性
this->age = age;
}
/*
最后的return *this 解释一下
this是一个指针,类型为A,指向当前对象,而返回的A&是一个引用,需要指明一个A类型的对象
而this就是指向的当前对象,*this解引用后就是一个对象
*/
A& add(A& a) {
this->age += a.age;
return *this;
}
};
int main() {
A a = 1;
A a2 = 1;
//链式编程??哈哈哈
//需要注意的是返回的是引用,而不是值传递,如果是值传递是没办法使用链式编程的
a.add(a2).add(a2).add(a2);
cout << a.age;
}
而当我们返回A而不是A&时,整个代码结果为2,因为只有第一次调用add方法是操作的a对象,当调用完第一次后返回的就是一个值传递赋值的新对象,而不是本体,所以最后输出2,只加了一次
空指针访问成员函数
c++允许空指针调用成员函数,需要注意
空指针可以访问成员函数,但是不能访问成员变量
class A {
public:
int age;
void print1() {
cout << "aughhhhh";
}
void print() {
cout << age;
//其实是cout<< this->age;
}
};
int main() {
A* a = NULL;
a->print(); //会报错
a->print1(); //正常运行
}
const修饰成员函数
常函数
- 当一个成员函数被const修饰称之为常函数
- 常函数内不能修改成员属性
- 成员属性声明时加mutable后在常函数中依然可以修改
常对象
- 声明对象前添加const称之为常对象
- 常对象只能调用常函数
class A {
public:
int age=2;
mutable int size=2; //添加关键字mutable就可以在常函数中修改
A() {
}
//this的本质是一个A* const a 的一个指针
//而在函数上添加const后
//this指针就是 const A* const a 那么值也不能改,指向也不能改
void test1() const {
//age = 100; 报错
size = 100;
}
void test2() {
age = 100;
size = 100;
}
};
int main() {
A a;
a.test1();
a.test2();
a.age = 100;
a.size = 100;
const A a1;
a1.test1();
//a1.test2(); 报错
//a1.age = 100; 报错
a1.size = 100;
}
总结:
普通函数对加没加mutable的属性都可以修改,普通对象可以调普通函数和常函数,普通对象可以直接修改属性和加了mutable的属性
常函数只能只能修改添加mutable的属性,常对象只能调用常函数,常对象只能修改添加mutable的属性
友元
有些私有属性也想让类外特殊的函数或类进行访问,可以使用友元,友元的目的就是让一个函数或类访问另一个类中的私有成员
关键字:friend
三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
1全局函数做友元
class People {
//声明这个全局函数是一个友元函数,是可以访问People类中的私有成员
friend void use_my_phone(People& p);
public:
string name;
People() {
name = "小王吧";
phone = "诺基亚";
}
private:
string phone;
};
void say_my_name(People& p) {
cout << p.name << endl;
}
void use_my_phone(People& p) {
cout << "使用手机:" << p.phone << endl;
}
int main() {
People p;
say_my_name(p);
use_my_phone(p);
}
2类做友元
class Phone {
//声明People类是友元,可以访问Phone的所有数据
friend class People;
public:
//声明我有一个函数是在类外面定义的
Phone();
private:
string name;
};
class People {
public:
string name;
People() {
name = "小王吧";
this -> phone = new Phone();
}
void use_my_phone() {
Phone& p=*(this->phone);
cout<<p.name;
}
private:
Phone* phone;
};
//类外定义函数
Phone::Phone() {
this->name = "诺基亚";
}
int main() {
People p;
p.use_my_phone();
}
`
3成员函数做有友元
#include <iostream>
#include<string>
using namespace std;
class People;
class Phone;
class People {
public:
string name;
People();
void use_my_phone();
private:
Phone* phone;
};
class Phone {
friend void People::use_my_phone();
public:
//声明我有一个函数是在类外面定义的
Phone() {
this->name = "诺基亚";
}
private:
string name;
};
//这一步必须在定义Phone之后,因为之前这个Phone还没有定义
People::People() {
name = "小王吧";
this->phone = new Phone();
}
void People::use_my_phone() {
Phone& p = *(this->phone);
cout << p.name;
}
int main() {
People p;
p.use_my_phone();
}
运算符重载
对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型
运算符是可以发生重载的,对应内置的数据类型的运算符是不能改变的
加号案例
局部函数
class A {
public:
int age;
A operator+(A& other_a) {
A temp;
temp.age = this->age + other_a.age;
return temp;
}
};
int main() {
A a1;
a1.age = 10;
A a2;
a2.age = 20;
A a3 = a1.operator+(a2);
//可以简化如下
A a4 = a1 + a2;
cout << a3.age << endl;
cout << a4.age << endl;
return 0;
}
全局函数
class A {
public:
int age;
};
A operator+(A& a,A& b) {
A temp;
temp.age = a.age + b.age;
return temp;
}
int main() {
A a1;
a1.age = 10;
A a2;
a2.age = 20;
A a3 =operator+(a1,a2);
A a4 = a1 + a2;
cout << a3.age << endl;
cout << a4.age << endl;
return 0;
}
加减乘除都是可以的,不复制上来了
左移运算符重载
左移运算符配合友元可以输出自定义的数据类型
类似java的toString()?
class A {
friend ostream& operator<<(ostream& cout, A& a);
public:
void set_age(const int _age) {
this->age = _age;
}
private :
int age;
};
ostream& operator<<(ostream& cout, A& a) {
cout << a.age;
return cout;
}
int main() {
A a1;
a1.set_age(1);
cout << a1<<endl; //简化
//operator<<(cout, a1); 本质
}
递增运算符重载
class MyNumber {
friend ostream& operator<<(ostream& o, MyNumber m);
public:
MyNumber() {
this->n = 0;
}
//前置递增
MyNumber& operator++() {
++(this->n);
return *this;
}
//后置递增
MyNumber operator++(int) {
MyNumber temp = *this;
(this->n)++;
return temp;
}
private:
int n;
};
//需要去掉MyNumber的&
/*
MyNumber& 会接收两个递增的结果:
一个MyNumber&一个MyNumber
引用接收引用可以,但是接收MyNumber不行,因为是修改引用的指向,而引用是不能修改指向的
MyNumbe接收引用可以,接收MyNumber也可以
或者设置为一个常量引用 const MyNumber& m
*/
ostream& operator<<(ostream& o, MyNumber m) {
o << m.n << endl;
return o;
}
int main() {
MyNumber m;
cout << ++m;
cout << ++m;
cout << m++;
cout << m;
}
前置递增返回引用,后置返回值
赋值运算符重载
c++编译器会给一个类添加至少4个函数
- 无参构造
- 无参析构
- 值拷贝函数
- 赋值运算符=,对属性进行值拷贝
如果类中有属性指向堆区,也会存在深浅拷贝问题
class A {
public:
A(int age) {
this->age = new int(age);
}
~A() {
if (age != NULL) {
delete age;
}
}
int* age;
};
int main() {
A a1(10);
A a2(20);
a2 = a1;
cout << *a1.age << endl;
cout << *a2.age << endl;
}
上面这段程序会有两个问题,1存在重复释放堆,2指针丢失指向
当a1走析构判断age指针不为空,释放了age,因为是值赋值,直接把a2的age指针指向了a1中的age地址
然后a2走析构判断也不为空,重复释放了一次
当a2=a1的操作走完,和问题1一样,值复制,直接把a2的age指针指向了a1中的age地址,那么a2的age指针的指向丢失了,直到程序关闭a2的age变量才会被释放
class A {
public:
A(int age) {
this->age = new int(age);
}
~A() {
if (age != NULL) {
delete age;
age = NULL;
}
}
A& operator=(A& a) {
//先判断当前指针是否有值,如果有先释放,防止指针丢失指向
if (this->age != NULL) {
delete this->age;
age = NULL;
}
this->age = new int(*a.age);
return *this;
}
int* age;
};
int main() {
A a1(10);
A a2(20);
A a3(30);
a3 = a2 = a1;
cout << *a1.age << endl;
cout << *a2.age << endl;
cout << *a3.age << endl;
}
关系运算符重载
利用关系运算符重载来实现两个自定义类型的对比操作
class A {
public:
A(int age) {
this->age = age;
}
int age;
bool operator==(A& ab) {
return ab.age == this->age;
}
bool operator!=(A& ab) {
return ab.age != this->age;
}
};
int main() {
A a1(10);
A a2(10);
A a3(30);
if (a1 == a2) {
cout << "eq";
}
if (a1 != a2) {
cout << "not_eq";
}
}
函数调用运算符重载
函数调用运算符()也可以重载,由于重载后使用方式和函数的调用很像,也被称为仿函数,仿函数没有固定写法
class A {
public:
A() {
}
A(string a) {
cout << "a" << a << endl;
}
//仿函数
void operator()(string name) {
cout << "oper" << name << endl;
}
//仿函数
void operator()(int a, int b) {
cout << "oper" << a + b << endl;
}
void print(string name) {
cout << "print" << name << endl;
}
};
int main() {
A a1("123");
a1("hghhhh");
a1(1, 2);
a1.print("123");
//使用匿名对象来调用仿函数,走完这行就回收
A()(1, 2);
}
继承
语法:class 类名:继承方式 父类名
- 公共继承public
- 保护继承protected
- 私有继承private
以继承方式作为最高权限上限,父类的权限除了private的全部都以继承方式作为权限上限
继承的对象模型
class Father {
public:
int a;
protected:
int b;
private :
int c;
};
class Son : public Father{
};
int main() {
cout << sizeof(Father) << endl;
cout << sizeof(Son) << endl;
}
结果两个12,也就是说即使子类访问不到的私有权限,但是还是会继承下去
继承中构造和析构顺序
class Father {
public:
Father() {
cout << "father create" << endl;
}
~Father() {
cout << "father delete" << endl;
}
public:
int a;
protected:
int b;
private:
int c;
};
class Son : public Father {
public:
Son() {
cout << "Son create" << endl;
}
~Son() {
cout << "Son delete" << endl;
}
};
int main() {
Son s;
}
father create
Son create
Son delete
father delete
- 父类创建
- 子类创建
- 子类销毁
- 父类销毁
继承同名成员处理方式
调用属性:对象.继承类名::属性
调用方法:对象.继承类名::方法();
不仅可以直接使用父级的,爷爷级的也可以,等等
class Grandpa {
public:
Grandpa() {
a = 300;
}
int a;
void print() {
cout << "g" << endl;
}
};
class Father : public Grandpa {
public:
Father() {
a = 200;
}
int a;
void print() {
cout << "f" << endl;
}
};
class Son : public Father {
public:
Son() {
a = 100;
}
int a;
void print() {
cout << "s" << endl;
}
};
int main() {
Son s;
cout << s.a << endl; //调自己属性
cout<<s.Grandpa::a<<endl; //父级属性
cout<<s.Father::a<<endl; //爷爷级属性
s.print(); //自己方法
s.Father::print(); //父级方法
s.Grandpa::print(); //爷爷级方法
}
如果子类中出现了和父类相同函数名称的函数,会隐藏掉父类中所有同名的函数,如果想要访问到父类中的成员需要加作用域
class Father {
public:
Father() {
}
int a;
void print() {
cout << "f" << endl;
}
void print(int pp) {
cout << "f-a:" << a << endl;
}
};
class Son : public Father {
public:
//可以在子类中设置父类的属性值
Son() {
Father::a = 100;
}
int a;
void print() {
cout << "s" << endl;
}
};
int main() {
Son s;
s.print();
s.Father::print();
s.Father::print(1);
}
而如果子类中没有和父类重名的方法,可以直接子类对象.父类中定义的函数();
class Father {
public:
Father() {
}
int a;
void print() {
cout << "f" << endl;
}
void print(int pp) {
cout << "f-a:" << a << endl;
}
};
class Son : public Father {
public:
//可以在子类中设置父类的属性值
Son() {
Father::a = 100;
}
int a;
};
int main() {
Son s;
s.print();
s.print(1);
}
继承同名静态成员处理方式
和非静态处理方式一样
class Father {
public:
Father() {
}
static int a;
static void print() {
cout << "f" << endl;
}
static void print(int pp) {
cout << "f-a:" << a << endl;
}
};
class Son : public Father {
public:
Son() {
}
static int a;
static void print() {
cout << "s" << endl;
}
};
int Son::a = 100;
int Father::a = 200;
int main() {
//通过对象访问
Son s;
cout << s.a << endl;;
cout << s.Father::a << endl;
s.print();
s.Father::print(1);
//直接通过类访问
cout << Son::a << endl;;
cout << Father::a << endl;
cout << Son::Father::a << endl;
Son::print();
Father::print(1);
Son::Father::print(1);
}
多继承
c++允许一个类继承多个类
语法:class 类名:继承方式 父类1,继承方式 父类2...
和单继承类似
class Father2 {
public:
Father2() {
}
static int a;
static void print() {
cout << "f" << endl;
}
static void print(int pp) {
cout << "f-a:" << a << endl;
}
};
class Father {
public:
Father() {
}
static int a;
static void print() {
cout << "f" << endl;
}
static void print(int pp) {
cout << "f-a:" << a << endl;
}
};
class Son : public Father,public Father2 {
public:
Son() {
}
static int a;
static void print() {
cout << "s" << endl;
}
};
int Son::a = 100;
int Father::a = 200;
int main() {
//通过对象访问
Son s;
cout << s.a << endl;;
cout << s.Father::a << endl;
cout << s.Father2::a << endl;
s.print();
s.Father::print(1);
s.Father2::print(1);
//直接通过类访问
cout << Son::a << endl;;
cout << Father::a << endl;
cout << Father2::a << endl;
cout << Son::Father::a << endl;
cout << Son::Father2::a << endl;
Son::print();
Father::print(1);
Father2::print(1);
Son::Father::print(1);
Son::Father2::print(1);
}
菱形继承
class Father {
public:
int a;
};
class Son1 : public Father {
};
class Son2 : public Father {
};
class Grandson :public Son1, public Son2 {
};
int main() {
Grandson g;
g.a = 100; //报错
}
因为存在二义性,我们可以指定使用那个类的a属性
int main() {
Grandson g;
g.Son2::a = 100;
g.Son1::a = 100;
}
但是两个父类里面都存在这个a属性,我们不需要两份,可以使用虚继承
语法:class 类名:virtual 权限修饰符 父类名
当使用virtual 来标识类为虚继承时,类中存放的变量就会变成指针,指向父类的变量地址
class Father {
public:
int a;
};
class Son1 : virtual public Father {
};
class Son2 : virtual public Father {
};
class Grandson :public Son1, public Son2 {
};
int main() {
Grandson g;
g.a = 100;
}
多态
- 静态多态:函数重载,运算符重载都属于静态多态
- 动态多态:派生类和虚函数实现运行时多态
区别:
- 静态多态函数地址早绑定: 编译阶段确定函数地址
- 动态多态函数地址晚绑定: 运行阶段确定函数地址
class Animal {
public:
void say() {
cout << "动物在说话"<<endl;
}
};
class Cat :public Animal {
public :
void say() {
cout << "猫在叫" << endl;
}
};
void doSay(Animal& animal) {
animal.say();
}
int main() {
Cat c;
doSay(c);
}
结果是动物在说话,并不是猫,虽然传入的是猫的对象
class Animal {
public:
//给需要的方法添加virtual
virtual void say() {
cout << "动物在说话"<<endl;
}
};
class Cat :public Animal {
public :
void say() {
cout << "猫在叫" << endl;
}
};
void doSay(Animal& animal) {
animal.say();
}
int main() {
Cat c;
doSay(c);
Animal a;
a.say();
}
当存在多态的情况会调用子类重写的方法,单独调用也正常
多态的满足条件:
- 存在继承关系
子类重写父类的虚函数
- 重写: 子类中 返回类型 函数名 函数参数类型/顺序/数量 和父类中一致
多态的使用: 父类指针指向子类对象
纯虚函数和抽象类
在多态中,通常父类的函数是没有意义的,单纯为了子类重写,那么我们可以将父类中的函数定义为纯虚函数
语法:virtual 返回值类型 函数名(函数形参) =0;
当一个类中存在纯虚函数,那么这个类称之为抽象类
抽象类特点
- 无法实例化
- 子类必须重写父类的纯虚函数,除非子类也是抽象类
class Animal {
public:
virtual void say() = 0;
};
class Cat :public Animal {
public:
void say() {
cout << "wdnmd" << endl;
};
};
void doSayP(Animal* animal) {
animal->say();
}
int main() {
Cat c1;
doSayP(&c1);
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆内存,那么父类的指针在释放时无法调用子类的析构方法
虚析构和纯虚析构
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
区别:如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名()=0;
类名::~类名(){}
class Animal {
public:
Animal() {
cout << "animal构造" << endl;
}
~Animal() {
cout << "animal析构" << endl;
}
virtual void say() = 0;
};
class Cat :public Animal {
public:
string* name;
Cat(string* _name) {
cout << "Cat构造" << endl;
this->name = _name;
}
~Cat() {
cout << "Cat析构" << endl;
if (this->name != NULL) {
delete this->name;
}
}
void say() {
cout << "wdnmd:" << *(this->name) << endl;
};
};
void doSayP(Animal* animal) {
animal->say();
delete animal;
}
int main() {
string* name = new string("hh");
Cat* c1=new Cat(name);
doSayP(c1);
}
animal构造
Cat构造
wdnmd:hh
animal析构
并没有走Cat类的析构,也就是说string* name 指向的内存地址并没有释放
class Animal {
public:
Animal() {
cout << "animal构造" << endl;
}
//给父类添加关键字virtual 标记是一个虚析构函数
virtual ~Animal() {
cout << "animal析构" << endl;
}
virtual void say() = 0;
};
animal构造
Cat构造
wdnmd:hh
Cat析构
animal析构
纯虚析构
class Animal {
public:
Animal() {
cout << "animal构造" << endl;
}
virtual ~Animal() = 0;
virtual void say() = 0;
};
Animal::~Animal() {
cout << "animal析构" << endl;
}
练习
class Memory {
public:
virtual void run() = 0;
virtual ~Memory() {
}
};
class MA :public Memory {
public:
void run() {
cout << "Ma的内存嘎嘎跑" << endl;;
}
~MA() {
cout << "MA 析构" << endl;
}
};
class MB :public Memory {
public:
void run() {
cout << "Mb的内存嘎嘎跑" << endl;;
}
~MB() {
cout << "MB 析构" << endl;
}
};
class Cpu {
public:
virtual void run() = 0;
virtual ~Cpu() {
}
};
class Intel :public Cpu {
public:
void run() {
cout << "intel no!" << endl;
}
~Intel() {
cout << "Intel 析构" << endl;
}
};
class Amd :public Cpu {
public:
void run() {
cout << "AMD yes!" << endl;
}
~Amd() {
cout << "amd 析构" << endl;
}
};
class Computer {
public:
Computer() {
}
virtual ~Computer() {
}
Cpu* cpu;
Memory* memory;
};
class LG :public Computer {
public:
LG(Cpu* _cpu, Memory* _memory) {
this->cpu = _cpu;
this->memory = _memory;
}
~LG() {
cout << "LG析构" << endl;
if (this->cpu != NULL) {
delete this->cpu;
this->cpu = NULL;
}
if (this->memory != NULL) {
delete this->memory;
this->memory = NULL;
}
}
};
void start(Computer& c) {
cout << "电脑正在嘎跑" << endl;
(c.cpu)->run();
(c.memory)->run();
}
int main() {
Cpu* c = new Intel();
Memory* m = new MB();
Computer* cm = new LG(c, m);
start(*cm);
delete cm;
}
电脑正在嘎跑
intel no!
Mb的内存嘎嘎跑
LG析构
Intel 析构
MB 析构
文件操作
包含头文件<fstream>
文件分为两种:文本文件,二进制文件
文本方式
#include <fstream>
int main() {
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "wdnmd";
ofs.close();
}
打开方式
- iso::in 读文件方式打开
- iso::out 写文件方式打开
- iso::ate 初始方式文件尾
- iso::app 追加方式写文件
- iso::trunc 如果文件存在,则先删除再创建
- iso::binary 二进制方式写
如果即写文件方式打开又是二进制方式写 可以添加 |
使用两个打开方式
读文件
#include <fstream>
int main() {
ifstream ifs;
ifs.open("test.txt", ios::in);
//判断文件是否打开成功
if (!ifs.is_open()){
cout << "打开文件失败";
return 1;
}
char buff[100] = {};
//读文件1
/*while (ifs >> buff) {
cout << buff << endl;
}*/
//2 读到哪里,读多大数据
//while (ifs.getline(buff,sizeof(buff))) {
// cout << buff << endl;
//}
//3
//string buff_string;
//while (getline(ifs, buff_string)) {
// cout << buff_string << endl;;
//}
//4
char c;
while ((c = ifs.get())!=EOF) { //end of file
cout << c;
}
ifs.close();
}
二进制方式
#include <fstream>
class A {
public:
char name[10];
};
int main() {
ofstream ofs("s.txt", ios::out | ios::binary);
//在构造中声明,打开的文件,打开方式
//ofs.open("s.txt", ios::out | ios::binary);
//结构体初始化方式,类也也一样
A ad = {"hhhh"};
ofs.write((const char*)&ad, sizeof(A));
ofs.close();
}
#include <fstream>
class A {
public:
char name[10];
};
int main() {
ifstream ifs("s.txt", ios::out | ios::binary);
//在构造中声明,打开的文件,打开方式
//ofs.open("s.txt", ios::out | ios::binary);
if (!ifs.is_open()) {
cout << "打开文件失败" << endl;
}
A a;
ifs.read((char*)&a, sizeof(A));
cout << a.name << endl;
ifs.close();
}