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;
}

运算符

  1. 算数运算符 处理四则运算
  2. 赋值运算符 将表达式赋值给变量
  3. 比较运算符 表达式的比较并返回一个bool值
  4. 逻辑运算符 用于根据表达式的值返回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;
	
}

数组名用途

  1. 可以获取整个数组在内存中的长度
  2. 可以获取内存中数组的首地址
#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];
		}
	}

}

函数(方法)

一个函数包含以下内容:

  1. 返回值类型
  2. 函数名称
  3. 参数列表
  4. 函数体
  5. 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,或者我们输出number1a的地址,也会发现不同,说明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;
}

函数分文件编写

将相同功能的函数单独放在一个文件中,使结构更加清晰

  1. 创建后缀为.h 的头文件
  2. 创建后缀为.cpp的源文件
  3. 在头文件中写函数声明
  4. 在源文件中写函数实现

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有三种情况

  1. 修饰指针:常量指针
  2. 修饰常量:指针常量
  3. 即修饰指针,又修饰常量

常量指针

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: 注意! 结构体结束}后面需要加一个;结尾

通过结构体创建变量的方式有三种:

  1. struct 结构体名 变量名
  2. struct 结构体名 变量名=
  3. 定义结构体时同时设置变量

方式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

对象的初始化和清理

构造函数和析构函数

构造函数语法:类名(){}

  1. 构造函数没有返回值也不写void
  2. 函数名称和类名相同
  3. 构造函数可以存在参数,因此可以发生重载
  4. 程序在初始化对象时会自动调用构造,无需手动调用,而且只会调用一次

析构函数语法:~类名(){}

  1. 析构函数没有返回值也不写void
  2. 函数名与类名相同,前面加上~
  3. 析构函数没有参数,因此不可能发生重载
  4. 程序在销毁对象时会调用析构函数,无需手动调用,而且只会调用一次
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//拷贝构造
}

拷贝构造函数调用时机

  1. 使用一个已经创建完的对象来初始化一个新对象
  2. 值传递的方式给函数传值
  3. 以值的方式返回局部变量

实体类:

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艹编译器会给一个类添加三个构造

  1. 默认构造,无参,函数体为空
  2. 默认析构函数,无参,函数体为空
  3. 默认拷贝构造函数,对属性值进行拷贝

如果自定义了构造函数,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个函数

  1. 无参构造
  2. 无参析构
  3. 值拷贝函数
  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
  1. 父类创建
  2. 子类创建
  3. 子类销毁
  4. 父类销毁

继承同名成员处理方式

调用属性:对象.继承类名::属性

调用方法:对象.继承类名::方法();

不仅可以直接使用父级的,爷爷级的也可以,等等

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();
}

当存在多态的情况会调用子类重写的方法,单独调用也正常

多态的满足条件:

  1. 存在继承关系
  2. 子类重写父类的虚函数
    1. 重写: 子类中 返回类型 函数名 函数参数类型/顺序/数量 和父类中一致

多态的使用: 父类指针指向子类对象

纯虚函数和抽象类

在多态中,通常父类的函数是没有意义的,单纯为了子类重写,那么我们可以将父类中的函数定义为纯虚函数

语法: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();

	
}     
posted @ 2022-10-23 11:07  Jame!  阅读(281)  评论(0编辑  收藏  举报