C++基础知识点

一 入门

1 c++初识

1.1 书写HelloWorld

基本代码框架

#include <iostream>
using namespace std;

int main()
{	
	cout << "hello world" << endl; //endl为换行
	system("pause");
	return 0;
}

注释同java

1.2 变量

变量存在的意义:方便我们管理内存空间
变量创建的语法:数据类型 变量名=变量初值
int a=10;

1.3 常量

用于记录程序中不可更改的数据
两种方式:
1:#define 宏常量:# define 常量名 常量值

  • 通常在文件上方定义,表示一个常量

2:const修饰的变量:const 数据类型 常量名=常量值

  • 通常在变量定义前加关键字const,修饰该变量为常量,不可修改

1.4 关键字

是c++中预先保留的单词

1.5 标识符命名规则

cpp规定给标识符命名时,有一套自己的规则

  • 标识符不能是关键字
  • 标识符智能由字母、数字、下划线组成
  • 第一个字符必须为字母或下划线
  • 标识符中字母区分大小写

2 数据类型

给变量分配合适的内存空间

2.1 整形

表示的是整数类型的数据

数据类型占用空间取值方位
short2字节-2^15 ~ 2^15-1
int4字节-2^31 ~ 2^31-1
longwindows为4字节,Linux为4字节(32位),8字节(64位)-2^31 ~ 2^31-1
long long8字节-2^63 ~ 2^63-1

2.2 sizeof关键字

可以统计数据类型所占内存大小

整形结论: short<int<=long<=long long

2.3 实型(浮点型)

用于表示小数

  • 1 单精度float
  • 2 双精度double

两者区别在于有效数字范围不同

数据类型占用空间有效数字范围
float4字节7位有效数字
double8字节15~16有效数字

默认输出一个小数,显示六位有效数字

//科学计数法
float f2=3e2; // 3*100;

2.4 字符型

字符型变量用于显示单个字符
char ch=‘a’;

  • 字符型变量只占用一个字节
  • 并不是把字符本身放到内存中存储,而是将ASCII码放入到存储单元

2.5 转义字符

用于表示一些不能显示出来的ASCII字符

//换行符 \n
// 反斜杠 \\
// 水平制表符  \t  作用可以整齐输出数据

2.6 字符串型

用于表示一串字符
两种风格

  • 1 c风格字符串 char 变量名[] = “字符串值”;
  • 2 c++风格字符串 string 变量名=“字符串值”;(头文件加#include< string >)

2.7 布尔类型 bool

代表真或假
bool占一个字节大小

bool flag=true;(本质为1)

2.8 数据的输入

用于从键盘获取数据

关键字:cin
语法:cin>>变量 n

3 运算符

用于执行代码的运算

运算符类型作用
算术运算符用于处理四则运算
赋值运算符用于将表达式的值赋给变量
比较运算符用于表达式的比较,并返回一个真值或假
逻辑运算符用于根据表达式的值返回真值或假值

3.1 算术运算符

用于处理四则运算

3.2 赋值运算符

用于将表达式的值赋给变量

3.3 比较运算符

用于表达式的比较,并返回一个真值或假值

3.4 逻辑运算符

用于根据表达式的值返回真值或假值

4 程序流程结构

  • 顺序结构:程序按照顺序执行,不发生跳转
  • 选择结构:依据条件是否满足,有选择的执行响应功能
  • 循环结构:依据条件是否满足,循环多次执行某段代码

4.1选择结构

4.1.1 if语句

执行满足条件的语句

  • 单行格式if语句 if(){}
  • 多行格式if语句 if(){}else{}
  • 多条件的if语句 if(){}else if(){}else{};
4.1.2 三目运算符

通过三目运算符实现简单的判断
语法:表达式1?表达式2:表达式3

4.1.3 switch语句

执行多条件分支语句
语法:

switch(表达式){
	case 结果1:执行语句;break;
	case 结果2:执行语句;break;
	...
	default:执行语句;break;
}

switch语句中表达式类型只能是整形或字符型

4.2 循环结构

4.2.1 while循环

满足循环条件,执行循环语句
语法:while(循环条件){循环语句}
解释:只要循环条件的结果为真,就执行循环语句

#include<ctime>
//添加随机数种子,利用当前系统时间生成随机数,防止每次都一样
srand((unsigned int)time(NULL));
rand()%100 //生成0~99随机数
4.2.2 do…while循环语句

满足循环条件,执行循环语句
语法:do{循环语句}while(循环条件);
与while的区别在于do…while会执行以此循环语句,再判断循环条件

4.2.3 for循环语句

满足循环条件,执行循环语句
语法:for(起始表达式;条件表达式;末尾循环体){循环语句;}

4.2.4 嵌套循环

在循环体中再嵌套一层循环,解决一些实际问题

4.3 跳转语句

4.3.1 break语句

用于跳出选择结构或者循环结构

4.3.2 continue语句

在循环语句中,跳过本次循环中余下未执行的语句,继续执行下一次循环

4.3.3 goto语句

可以无条件跳转语句
语法:goto 标记;

5 数组

5.1 概述

所谓数组,就是一个集合,里面存放了相同类型的数据元素

  • 特点1:数组中的每个数据元素都是相同的数据类型
  • 特点2:数组是由连续的内存位置组成的

5.2 一维数组

5.2.1 一维数组定义方式

三种方式:

  • 1.数据类型 数组名[数组长度];
  • 2.数据类型 数组名[数组长度]={值1,值2…};
  • 3.数据类型 数组名[] ={值1,值2 …};
5.2.2 一维数组数组名

一维数组名称的用途

  • 1.可以统计整个数组在内存中的长度 sizeof(arr)
  • 2.可以获取数组在内存中的首地址
    数组长度可以用sizeof(arr)/sizeof(arr[0])-1
5.2.3 冒泡排序

作用:最常用的排序算法,对数组内元素进行排序

  • 1.比较相邻元素。如果第一个比第二个大,就交换他们两个
  • 2.对每一对相邻元素做同样的工作,执行完毕后,找到第一个最大值
  • 3.重复以上的步骤,每次比较次数-1,直到不需要比较

5.3 二维数组

5.3.1 二维数组定义方式

四种方式

  • 1.数据类型 数组名[行数][列数]

  • 2.数据类型 数组名[行数][列数] ={ {数据1,数据2},{数据3,数据4} };

    1. 数据类型 数组名[行数][列数]={数据1,数据2,数据3,数据4};
  • 4.数据类型 数组名[][列数]={数据1,数据2,数据3,数据4};

建议第二种,提高代码可读性

5.3.2 二维数组数组名
  • 查看二维数组所占内存空间

  • 获取二维数组首地址

查看二维数组第一个元素地址

(int)&arr[0][0] //取址符&

6 函数

6.1 概述

将一段经常使用的代码封装起来,减少重复代码

6.2 函数的定义

函数的定义一般主要有5个步骤

  • 1.返回值类型
  • 2.函数名
  • 3.参数列表
  • 4.函数体语句
  • 5.return表达式

语句

返回值类型 函数名(参数列表)
{
	函数体语句
	return 表达式
}

6.3 函数的调用

使用定义好的函数
语法:函数名(参数)

6.4 值传递

  • 所谓值传递,就是函数调用时实参会将数值传入给形参
  • 值传递时,如果形参发生改变,并不会影响实参

6.5 函数的常见样式

常见的函数样式有四种

  • 1.无参无返
  • 2.有参无返
  • 3.无参有反
  • 4.有参有反

6.6 函数的声明

告诉编译器函数名称及如何调用函数。函数的实际主题可以单独定义

  • 函数的声明可以多次,但是函数的定义只能有一次
int max(int a,int b); // 提前告诉编译器函数存在,这样在主函数后可以定义函数

6.7 函数的分文件编写

让代码结构更加清晰
函数分文件一般有三个步骤

  • 1:创建后缀名为.h的头文件
  • 2:创建后缀名为.cpp的源文件
  • 3: 在头文件中写函数的声明
  • 4: 在源文件中写函数的定义

7 指针

7.1 指针的基本概念

指针的作用:可以通过指针直接访问内存

  • 内存的编号是从0开始记录的,一般用十六进制数字表示
  • 可以利用指针变量保存地址

7.2 指针变量的定义和使用

语法: 数据类型 * 指针变量名;

使用:可以通过解析引用的方式来找到指针指向的内存
指针前加*代表解引用,找到指针指向的内存中的数据

7.3 指针所占内存空间

32 位占4个字节空间
64位占 8个字节空间

7.4 空指针和野指针

空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
0~255是系统占用的

野指针:指针变量指向非法的内存空间

// 在程序中,尽量避免出现野指针
int *p=(int *)0x1100;

7.5 const修饰指针

const修饰指针有三种情况:
1.const修饰指针 --常量指针

const int *p =&a;

特点:指针的指向可以修改,但是指针指向的值不可以更改
2.const修饰常量 --指针常量

int * const p= &a;

特点:指针的指向不可以改,指针指向的值可以改

3.const修饰指针,又修饰常量

const int* const p=&a;

特点:指针的指向和指针指向的值都不可以更改

7.6 指针数组

利用指针访问数组中元素

#include<iostream>
using namespace std;
int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr; //arr就是数组首地址
	cout << *p << endl;
	p++;
	cout << *p << endl; // 让指针向后偏移4个字节
	int* p2 = arr; // 利用指针循环遍历
	for (int i = 0; i < 10; i++) {
		cout << *p2;
		p2++;
	}
	system("pause");
	return 0;
}

7.7 指针和函数

作用:利用指针作函数参数,可以修改实参的值

地址传递:
如果是地址传递,可以修饰实参

总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递

7.8 指针、数组、函数

8 结构体

8.1 结构体的基本概念

结构体属于用户自定义的数据类型,允许用户存储不同的数据结构

8.2 结构体的定义和使用

语法:struct 结构体名 {结构体成员变量};
通过结构体创建变量的三种方式:

  • struct 结构体名 变量名
  • struct 结构体名 变量名={成员1值,成员2值···};
  • 定义结构体时顺便创建变量
#include<iostream>
using namespace std;

struct Student
{
	//成员列表
	string name;
	int age;
	int score;
}s3;

int main()
{
	//1 创建方式
	// struct Student s1; 关键字struct可以省略
	struct Student s1;
	s1.name = "张三";
	s1.age = 18;
	s1.score = 88;
	cout << s1.name << s1.age<< s1.score << endl;
	// 2 struct Student s2={```}
	struct Student s2 = { "里斯",19,99 };
	cout << s2.name << s2.age << s2.score << endl;
	// 3 创建结构体同时创建变量
	s3.name = "李白";
	s3.age = 18;
	s3.score = 99;
	cout << s3.name << s3.age << s3.score << endl;
	system("pause");
	return 0;
}

总结1:定义结构体时的关键字时struct,不可省略
总结2:创建结构体变量时,关键字struct可以省略
总结3:结构体变量利用操作符“.”访问成员

8.3 结构体数组

将自定义的结构体放入到数组中方便维护
语法:struct 结构体名 数组名[元素个数]={ {}, {}, {}…}

#include<iostream>
using namespace std;

struct Student
{
	string name;
	int age;
	int score;
};
int main()
{
	struct Student stdArray[3] = {
		{"张三",18,100},
		{"里斯",28,99},
		{"王五",10,100}
	};
	stdArray[2].name = "赵六";
	stdArray[2].age = 80;
	stdArray[2].score = 60;
	for (int i = 0; i < 3; i++) {
		cout << stdArray[i].name << stdArray[i].age << stdArray[i].score << endl;
	}
	system("pause");
	return 0;
}

8.4 结构体指针

通过指针访问结构体中的成员

  • 利用操作符->可以通过结构体指针访问结构体属性
#include<iostream>
using namespace std;
struct Student
{
	string name;
	int age;
	int score;
};
int main()
{
	struct Student s = { "张三",18,99 };
	struct Student *p = &s;
	cout << p->name << p->age << p->score << endl;
	system("pause");
	return 0;
}

8.5 结构体嵌套结构体

结构体中的成员可以是另一个结构体

8.6 结构体做函数参数

将结构体作为参数向函数中传递
传递方式有两种:

  • 值传递
  • 地址传递
#include<iostream>
using namespace std;

struct Student
{
	string name;
	int age;
	int score;
};
// 1 值传递
void printStudent1(struct Student s)
{
	cout << s.name << s.age << s.score << endl;
};
//2 地址传递
void printStudent2(struct Student *p)
{
	cout << p->name << p->age<< p->score << endl;
};
int main()
{
	struct Student s = {"董斌",18,99};
	printStudent1(s);
	printStudent2(&s);
	system("pause");
	return 0;
}

** 总结:如果不想修改主函数中的数据,用值传递,反之用地址传递**

8.7 结构体中const使用场景

用const防止误操作,将函数中形参改为指针,可以减少内存空间,而且不会复制新的副本出来。

#include<iostream>
using namespace std;

struct Student
{
	string name;
	int age;
	int score;
};
void printStudent2(const struct Student* p)
{
	// 加入const之后,可以防止误操作
	cout << p->name << p->age << p->score << endl;
};
int main()
{
	struct Student s = {"董斌",18,99};
	printStudent2(&s);
	system("pause");
	return 0;
}

二 C++核心编程

主要针对C++面向对象编程技术

1 内存分区模型

c++程序在执行时,将内存大方向划分为四个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:
不同区域存放的数据,赋予不同的生命周期,给我们更加大的灵活编程

1.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
          存放 CPU执行的机器指令。
          代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
          代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:
          全局变量和静态变量存放于此
          全局区还包含了常量区,字符串常量和其他常量也存放在此
         该区域的数据在程序结束后由操作系统回收

#include<iostream>
using namespace std;
// 全局变量
int g_a = 10;
int g_b = 10;
const int c_g_a = 10;
int main() {
	// 全局区

	//全局变量、静态变量、常量

	//创建普通局部变量
	int a = 10;
	int b = 10;

	cout << "局部变量a的地址为: " << (long)&a << endl;
	cout << "局部变量b的地址为: " << (long)&b << endl;
	cout << "全局变量g_a的地址为: " << (long)&g_a << endl;
	cout << "全局变量g_b的地址为: " << (long)&g_b << endl;
	// 静态变量 在普通变量前加static
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a的地址为: " << (long)&s_a << endl;
	cout << "静态变量s_b的地址为: " << (long)&s_b << endl;
	//常量
	//字符串常量
	cout << "字符串常量的地址为: " << (long)&"hello world" << endl;
	//const修饰的变量
	//const修饰的全局变量
	cout << "全局常量的地址为: " << (long)&c_g_a << endl;

	const int c_l_a = 10;
	cout << "局部常量的地址为: " << (long)&c_l_a << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
总结:

  • c++中在程序运行前分为全局区和代码区
  • 代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放const修饰的全局常量和字符串常量

1.2 程序运行后

栈区:

  • 由编译器自动分配释放,存放函数的参数值,局部变量等
  • 注意事项:不要放回局部变量的地址,栈区开辟的数据由编译器自动释放
#include<iostream>
using namespace std;
// 栈区数据注意事项 -- 不要释放局部变量地址
//数据由编译器管理开辟和释放
int* fun(int b) { // 形参数据也会放在栈上
	b = 100;
	int a = 10;//局部变量,存放在栈区,栈区的数据在函数执行完自动释放
	return &a; //返回局部变量的地址
}
int main() {
	int* p = fun(1);
	cout << *p << endl; //第一次可以打印正确,编译器做了保留
	cout << *p << endl; //第二次这个数据就不再保留

	system("pause");
	return 0;
}

堆区:

  • 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
  • 在C++中主要利用new在堆区开辟内存
#include<iostream>
using namespace std;


int* func() {
	// 利用new关键字,可以将数据开辟到堆区
	//指针,本质为变量,放在栈上,指针保存的数据是放在堆区
	int *p=new int(10);
	return p;
}
int main() {
	//在堆区开辟数据
	int* p = func();
	cout << *p << endl;
	system("pause");
	return 0;
}

总结:

  • 堆区数据由程序员管理开辟和释放
  • 堆区数据利用new关键字进行开辟内存

1.3 new 操作符

c++利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针

#include<iostream>
using namespace std;
// 1 new基本语法
int* fun() {
	//在堆区创建整形数据
	int *p=new int(10);
	return p;
}
void test01() {
	int* p = fun();
	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;
	//堆区数据由程序员管理开辟,程序员管理释放
	//如果想释放堆区的数据,利用关键字delete
	delete p;
	/*cout << *p << endl;*/ //内存已经释放,再次访问就是非法操作,会报错
}

void test02() {
	//创建10整形数据的数组,在堆区
	int *arr=new int[10]; //10代表数组有10个空间
	for (int i = 0; i < 10; i++) {
		arr[i] = i + 100;
	}
	for (int i = 0; i < 10; i++) {
		cout << arr[i] << endl;
	}
	//释放堆区数组
	//释放数组的时候,要加[]才可以
	delete[] arr;
}
int main() {
	test02();
	system("pause");
	return 0;
}

2 引用

2.1引用的基本使用

作用:给变量起别名
语法:数据类型 &别名=原名

#include<iostream>
using namespace std;

int main() {
	//引用
	int a = 10;
	int& b = a;
	cout << a << endl;
	cout << b << endl;
	b = 100;
	cout << a << endl;
	cout << b << endl;
	system("pause");
	return 0;
}

2.2引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变
#include<iostream>
using namespace std;

int main() {
	int a = 10;
	//1引用必须初始化
	int& b = a;
	//2引用在初始化后,不可以改变
	int c = 20;
	system("pause");
	return 0;
}

2.3 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参

#include<iostream>
using namespace std;

//交换函数

// 1.值传递
void swap01(int a,int b) {
	int temp = a;
	a = b;
	b = temp;
}

// 2、地址传递
void swap02(int *a,int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3、引用传递

void swap03(int &a,int&b) {
	int temp = a;
	a = b;
	b = temp;
}
int main() {
	int a = 10;
	int b = 20;
	cout << a << endl;
	cout << b << endl;
	swap03(a, b); //引用传递,形参会修饰实参的
	cout << a << endl;
	cout << b << endl;
	
	system("pause");
	return 0;
}

总结:通过引用参数产生的效果同按地址传递是一样的,引用的语法更清楚简单。

2.4 引用做函数返回值

作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值

#include<iostream>
using namespace std;
int& test01() {
	int a = 10;
	return a;
}
//函数调用可以作为左值
int& test02() {
	static int a = 10;//全局区,程序结束后系统释放
	return a;
}
int main() {
	int& ref = test02();
	cout << ref << endl; //第一次正确,编译器做了保留
	cout << ref << endl;	//第二次错误,因为a的内存已经释放
	test02() = 1000; //如果函数返回值是引用,这个函数条用可以作为左值
	cout << ref << endl; 
	cout << ref << endl;
	system("pause");
	return 0;
}

2.5 引用的本质

本质:引用的本质在C++内部实现是一个指针常量

结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

2.6 常量引用

作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,方式形参改变实参

#include<iostream>
using namespace std;
void swap(const int &value) {
	// value = 1000;
	cout << value << endl;
}
int main() {
	// 加上const之后,编译器将代码修改int temp=10,const int &ref=temp;
	//const int& ref = 10; //引用必须引一块合法的内存空间
	//ref=10; // 加入const之后变为只读,不可以修改
	int a = 10;
	swap(a);
	cout << a << endl;
	system("pause");
	return 0;
}

3 函数提高

3.1 函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名 (参数=默认值){}

#include<iostream>
using namespace std;
//函数默认参数
//如果我们自己传入数据,就用自己的数据,如果没有,那么用默认值
int func(int a, int b=20, int c=30) {
	return a + b + c;
}

// 注意事项
//1,如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
// int func2(int a=10, int b, int c) {
// return a + b + c;}
//2.如果函数声明有默认参数,函数实现就不能有默认函数
//声明和实现只能一个有默认参数
//int func2(int a=10, int b=10);
//int func2(int a=10, int b=10) {
//	return a + b;
//}
int main() {
	cout << func(10) << endl;
	
	system("pause");
	return 0;
}

3.2 函数占位参数

c++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法: 返回值类型 函数名(数据类型){}

3.3 函数重载

3.3.1函数重载概述

作用:函数名可以相同,提高复用性
函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同或者个数不同或者顺序不同

注意:函数的返回值不可以作为函数重载的条件

#include<iostream>
using namespace std;
void func() {
	cout << "func调用" << endl;
}
void func(int a) {
	cout << "funca调用!" << endl;
}
void func(double a) {
	cout << "double调用!" << endl;
}
void func(int a,double b) {
	cout << "1调用!" << endl;
}
void func(double a, int b) {
	cout << "2调用!" << endl;
}
//注意事项:函数返回值不可以作为函数重载的条件
int main() {
	
	func(1.0,1);
	system("pause");
	return 0;
}
3.3.2函数重载注意事项
  • 引用作为重载条件
  • 函数重载碰到函数默认参数
#include<iostream>
using namespace std;
//函数重载注意事项
//1.引用作为重载条件
void func(int &a) {
	cout << "func(&a)调用" << endl;
}
void func(const int& a) {
	cout << "func(const &a)调用" << endl;
}
//2.函数重载碰到默认参数
void func2(int a,int b=10) {
	cout << "func(int a)的调用" << endl;
}
void func2(int a) {
	cout << "func(int a)的调用" << endl;
}
int main() {
	/*int a = 10;
	func(a);*/   // func(&a)调用
	func(10);    // func(const &a)调用
	func2(10);  //函数重载碰到默认参数,出现二义性,报错,尽量避免这种情况
	system("pause");
	return 0;
}

4 对象

C++面向对象的三大特性:封装、继承、多态
C++认为万事万物皆为对象,对象上有其属性和行为

4.1封装

4.1.1 封装的意义

封装是C++面向对象三大特性之一
封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

封装意义一:
  在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限:属性/行为};

#include<iostream>
using namespace std;
const double PI = 3.14;
//设计一个圆类,求圆的周长
//圆求周长公式:2*pi*半径
//class代表设计一个类,类后紧跟着是类名称
class Circle {
	//访问权限
public:
	//属性
	//半径
	int m_r;
	//行为
	//获取圆的周长
	double calculateZC() {
		return 2 * PI * m_r;
	}
};
int main() {
	 //通过圆类 创建具体的圆(对象)
	//实例化:通过一个类创建一个对象的过程
	Circle c1;
	// 给圆对象的属性赋值
	c1.m_r = 10;
	cout << "圆的周长为:" << c1.calculateZC() << endl;
	system("pause");
	return 0;
}

封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  • 1 public 公共权限 成员类内可以访问,类外可以访问
  • 2 protected 保护权限 成员 类内可以访问,类外不可以访问 儿子也可以访问父亲保护内容
  • 3 private 私有权限 成员 类内可以访问,类外不可以访问 儿子不可以访问父亲私有内容
#include<iostream>
using namespace std;
// 三种访问权限
// 公共权限 public 成员类内可以访问,类外可以访问
// 保护权限 protected 成员 类内可以访问,类外不可以访问 儿子也可以访问父亲保护内容
// 私有权限 private 成员 类内可以访问,类外不可以访问 儿子不可以访问父亲私有内容

class Person {
public:
	//公共权限
	string m_name; //姓名
protected:
	//保护权限
	string m_Car;//汽车
private:
	//保护权限
	int m_Password;//银行卡密码
public:
	void func() {
		m_name = "张三";
		m_Car = "拖拉机";
		m_Password - 123456;

	}
};
int main() {
	//实例化具体对象
	Person p1;
	p1.m_name = "李四";
	//p1.m_Car = "奔驰"; // 保护权限内容,在类外访问不到
	//p1.m_Password = 1232; //私有权限内容,在类外访问不到

	system("pause");
	return 0;
}
4.1.2 struct和class区别

在C++中struct和class唯一的区别就在于默认访问权限不同
区别:

  • struct 默认权限为公共
  • class 默认权限为私有
#include<iostream>
using namespace std;
class C1 {
	int m_A; // 默认权限是私有
};
struct C2 {
	int m_A;//默认权限是公有
};
int main() {
	//struct 和class区别
	//struct默认权限是 公共 public
	//class默认权限是 私有 private
	C1 c1;
	/*c1.m_A = 100;*/
	C2 c2;
	c2.m_A = 100;
	system("pause");
	return 0;
}

4.1.3 成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性

4.2 对象的初始化和清理

C++的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置

4.2.1 构造函数和析构函数

   对象的初始化和清理是两个非常重要的安全问题。

  • 一个对象或者变量没有初始状态,对其使用后果是未知
  • 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现

  • 构造函数:主要作用在于创建对象时为对象的成员赋值,构造函数由编译器自动调用,无需手动调用
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

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

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

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

  • 1.析构函数,没有返回值不写void
  • 2.函数名称与类名相同,在名称前加上符号~
  • 3.析构函数不可以由参数,因此不可以发生重载
  • 4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<iostream>
using namespace std;
//对象的初始化和情理
//1.构造函数 进行初始化操作
class Person {
	// 1.1 构造函数
	//没有返回值 不用写Void
	//函数名 与类名相同
	//构造函数可以有参数,可以发生重载
	//创建对象的时候,构造函数会自动调用,而且只调用一次
public:
	Person() {
		cout << "构造函数调用" << endl;
	}

	//2.析构函数 进行清理的操作
	//没有返回值 不写void
	//函数名和类名相同 在名称前加~
	//析构函数不可以有参数的,不可以发生重载
	//对象销毁前 会自动调用析构函数,而且只会调用一次
	~Person() {
		cout << "析构函数调用" << endl;
	}
};

void test01() {
	Person p;  //在栈上对象,test01执行完毕后,释放这个对象
}

int main() {
	test01();
	system("pause");
	return 0;
}

4.2.2构造函数的分类及调用

两种分类方式:

  • 按参数分为:有参构造和无参构造
  • 按类型分为:普通构造和拷贝构造

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法
#include<iostream>
using namespace std;

//1 构造函数的分类及调用
//分类
// 按照参数分类 无参构造(默认构造)和有参构造
//按照类型分类 普通构造 拷贝构造
class Person {
	// 构造函数
public:
	Person() {
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a) {
		age = 10;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p) {
		age = p.age;
		cout << "Person的拷贝构造函数调用" << endl;
	}
	~Person() {
		cout << "Person的析构函数调用" << endl;
	}
	int age;

};
// 调用
void test01() {
	//1、括号法
	//Person p1; //默认构造函数调用
	//Person p2(10); // 有参构造函数
	//拷贝构造函数
	/*Person p3(p2);*/
	//注意事项1
	//调用默认构造函数时候,不要加()
	// 创建不了对象,下面这个代码,编译器会认为是一个函数的声明,不会认为在创建对象
	/*Person p1();*/
	/*cout << "p2的年龄:" << p2.age << endl;
	cout << "p3的年龄:" << p3.age << endl;*/
	//2、显示法

	//Person p1;
	//Person p2 = Person(10); //有参构造
	拷贝构造
	//Person p3 = Person(p2);
	/*Person(10);*///匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象
	/*cout << "aaaaa" << endl;*/

	//注意事项2
	//不要利用拷贝构造函数 初始化匿名对象 编译器会认为Person(p3)==Person p3,对象声明
	/*Person(p3);*/

	//3、隐式转换法
	Person p4 = 10; //相当于写了Person p4=Person(10);
	Person p5 = p4; // 拷贝构造

	
}


int main() {
	test01();
	system("pause");
	return 0;
}

4.2.3 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常由三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
#include<iostream>
using namespace std;

//拷贝构造函数调用时机


class Person {
public:
	Person() {
		cout << "默认构造函数" << endl;
	}
	Person(int age) {
		m_Age = age;
	}
	Person(const Person& p) {
		cout << "Person拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
	}
	~Person() {
		cout << "析构函数调用" << endl;
	}
	int m_Age;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
	Person p1(10);
	Person p2(p1);
	cout << "p2年龄为:" << p2.m_Age << endl;
}

//2、值传递的方式给函数参数传值
void doWork(Person p) {

}
void test02() {
	Person p;
	doWork(p);
}
//3、值方式返回局部对象
Person doWork2() {
	Person p1;
	cout << (int)&p1 << endl;
	return p1;
}
void test03() {
	Person p = doWork2();
	cout << (int)&p << endl;
}


int main() {
	/*test01();*/
	/*test02();*/
	test03();
	system("pause");
	return 0;
}

4.2.4 构造函数调用规则

默认情况下,C++编译器至少给一个类添加三个函数

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

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
#include<iostream>
using namespace std;

//构造函数调用规则
//1.创建一个类,C++编译器会给每个类都添加至少三个函数
// 默认构造(空实现)
// 析构函数(空实现)
// 拷贝构造 (值拷贝)

//2、如果我们写了有参构造函数,编译器就不再提供默认构造,仍提供拷贝构造函数
//如果写了拷贝构造函数,编译器就不再提供其他构造函数
class Person {
public:
	/*Person() {
		cout << "默认构造函数" << endl;
	}*/
	Person(int age) {
		m_Age = age;
	}
	/*Person(const Person& p) {
		cout << "Person拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
	}*/
	~Person() {
		cout << "析构函数调用" << endl;
	}
	int m_Age;
};
//void test01() {
//	Person p;
//	p.m_Age = 18;
//	Person p2(p);
//	cout << "p2年龄为" << p2.m_Age << endl;
//}
void test02() {
	Person p1(18);
	Person p2(p1);
	cout << "p2年龄为" << p2.m_Age << endl;
}


int main() {
	/*test01();*/
	test02();
	system("pause");
	return 0;
}

4.2.5 深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作
(问题:堆区的内存重复释放,用深拷贝解决)
在这里插入图片描述

深拷贝:在堆区重新申请空间,进行拷贝操作

#include<iostream>
using namespace std;
//深拷贝和浅拷贝

class Person {
public:
	Person() {
		cout << "默认构造函数" << endl;
	}
	Person(int age,int height) {
		m_Age = age;
		m_Height=new int(height);
		cout << "有参构造函数" << endl;
	}
	//自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person& p) {
		cout << "Person拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height; 编译器默认实现是这行代码
		//深拷贝操作
		m_Height=new int(*p.m_Height);
	}
	~Person() {
		//析构代码,将堆区开辟数据做释放操作
		if (m_Height != NULL) {
			delete m_Height;
			m_Height = NULL;
		}
		cout << "析构函数调用" << endl;
	}
	int m_Age;
	int* m_Height;
};
void test01() {
	Person p1(18,160);
	cout << "p1的年龄为" << p1.m_Age<<"身高为:" <<*p1.m_Height<< endl;
	Person p2(p1);
	cout << "p2的年龄为" << p2.m_Age <<"身高为:" <<*p1.m_Height <<endl;
}


int main() {
	test01();
	system("pause");
	return 0;
}

总结:如果属性由在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

4.2.6初始化列表

作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)…{}

#include<iostream>
using namespace std;

//初始化列表
class Person {
public:
	/*Person(int a, int b, int c) {
		m_A = a;
		m_B = b;
		m_C = c;
	}*/
	//初始化列表初始化属性
	Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) {

	}
	int m_A;
	int m_B;
	int m_C;
};
void test01() {
	/*Person p(10,20,30);*/
	Person p(10,20,30);
	cout << p.m_A << endl;
	cout << p.m_B << endl;
	cout << p.m_C << endl;

};

int main() {
	test01();
	system("pause");
	return 0;
}

4.2.7 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如

class A{}
Class B{
     A a;
}
#include<iostream>
#include<string>
using namespace std;
//类对象作为类成员
//手机类
class Phone {
public:
	Phone(string name) {
		m_PName = name;
		cout << "手机构造函数" << endl;
	}
	~Phone() {
		cout << "phone析构函数" << endl;
	}
	string m_PName;
};
class Person {
public:
	// Phone m_Phone=pName 隐式转换法
	Person(string name, string pName) :m_Name(name),m_Phone(pName){
		cout << "人的构造函数" << endl;
	}
	~Person() {
		cout << "person析构函数调用" << endl;
	}
	//姓名
	string m_Name;
	//手机
	Phone m_Phone;
};
//当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构顺序与构造相反
void test01() {
	Person p("Zhaga", "sangxing");
	cout << p.m_Name << endl;
	cout << p.m_Phone.m_PName << endl;
};
int main() {
	test01();
	system("pause");
	return 0;
}

4.2.8静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
   静态成员分为:

  • 静态成员变量:
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
	#include<iostream>
#include<string>
using namespace std;


class Person {
public:
	// 所有对象共享同一份数据 
	// 编译阶段就分配内存
	// 类内声明,类外初始化操作
	static int m_A;
	//静态成员变量也是有访问权限的
private:
	static int m_B;

};
int Person::m_A = 100;
int Person::m_B = 200;
void test01() {
	Person p1;
	cout << p1.m_A << endl;
	Person p2;
	p2.m_A = 200;
	cout << p1.m_A << endl;
}
void test02() {
	//静态成员变量不属于某个对象上,所有对象都共享同一份数据
	//因此静态成员变量有两种访问方式
	// 1.通过对象进行访问
	//Person p;
	//cout << p.m_A << endl;
	// 2.通过类名进行访问
	cout << Person::m_A << endl;
	// cout << Person::m_B << endl; 类外访问不到私有静态
}


int main() {
	/*test01();*/
	test02();
	system("pause");
	return 0;
}
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
#include<iostream>
#include<string>
using namespace std;
//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量

class Person {
public:
	//静态成员函数
	static void func() {
		m_A = 200; //静态成员函数可以访问静态成员变量
		//m_B = 200; 静态成员函数不可以访问 非静态成员变量,无法区分到底是那个对象的m_B属性
		cout << "static void func调用" << endl;
	}
	static int m_A;
	int m_B;//非静态成员变量

	// 静态成员函数也是有访问权限的
private:
	static void func2() {
		cout << "func2调用" << endl;
	}

};
int Person::m_A = 100;
void test01() {
	//1.通过对象进行访问
	Person p;
	p.func();
	//2.通过类名进行访问
	Person::func();
	// Person::func2();类外访问不到私有静态成员函数
}


int main() {
	test01();
	system("pause");
	return 0;
}

4.3 C++对象模型和this指针

4.3.1成员变量和成员函数分开存储

在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
空对象大小为1

#include<iostream>
#include<string>
using namespace std;
// 成员变量和成员函数分开存储

class Person {
	int m_A;//非静态成员变量 属于类的对象上
	static int m_B; //静态成员变量 不属于类对象上
	void func() {

	}//非静态函数 不属于类对象上
	static void func2() {

	} // 静态成员函数,不属于类对象上
};
int Person::m_B = 0;

void test01() {
	Person p;
	//空对象占用内存空间为:1
	// C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
	//每个空对象有独一无二的内存地址
	cout << sizeof(p) << endl;
}
void test02() {
	Person p;
	cout << sizeof(p) << endl;
}


int main() {
	// test01();
	test02();
	system("pause");
	return 0;
}

4.3.2 this指针概念

this指针指向被调用的成员函数所属的对象
this指针是隐含每个一非静态成员函数内的一种指针
this指针不需要定义,直接使用即可

this指针作用:

  • 当形参和成员变量同名时,可以用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	Person(int age) {
		//this指针指向被调用的成员函数所属的对象
		this->age = age;
	}
	Person& PersonAddPerson(Person &p) {
		this->age += p.age;
		return *this;
	}
	int age;
};
// 1 解决名称冲突
void test01() {
	Person p1(18);
	cout << p1.age << endl;
}
// 2 返回对象本身用*this
void test02() {
	Person p1(10);
	Person p2(10);
	// 链式编程
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << p2.age << endl;
}
int main() {
	test02();
	system("pause");
	return 0;
}

4.3.3 空指针访问成员函数

C++空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断代码的健壮性

#include<iostream>
#include<string>
using namespace std;

//空指针调用成员函数
class Person {
public:
	void showName() {
		cout << "Person" << endl;
	}
	void showAge() {
		//报错原因:传入的指针为空
		if (this == NULL) {
			return;
		}
		cout << this->m_Age << endl;
	}
	int m_Age;
};

// 1 解决名称冲突
void test01() {
	Person* p = NULL;
	p->showName();
	p->showAge();
}

int main() {
	test01();
	system("pause");
	return 0;
}

4.3.4 const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数种依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
#include<iostream>
#include<string>
using namespace std;

//常函数

class Person {
public:
	//this指针的本质 是指针常量 指针的指向是不可以修改的
	// Person *const this;
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
	void showPerson() const {
		this->m_B = 100;
		// this->m_A = 100;
		//this = NULL; //this指针不可以修改指针的指向
	}
	void func() {

	}
	int m_A;
	mutable int m_B; //特殊变量,即使在常函数种,也可以修改这个值,加上关键字mutable

};

void test01() {
	Person p;
	p.showPerson();
}
//常对象
void test02() {
	const Person p; //在对象前加const,变为常对象
	// p.m_A = 100;
	p.m_B = 100; //m_B是特殊值,即使在常对象下也可以修改
	p.showPerson();
	//p.func(); 常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main() {
	test01();
	system("pause");
	return 0;
}

4.4 友元

友元的目的就是让一个函数或者类访问另一个类中私有成员

友元关键字friend

友元的三种实现

  • 全局函数作友元
  • 类做友元
  • 成员函数做友元

4.4.1 全局函数做友元

#include<iostream>
#include<string>
using namespace std;
//建筑物类
class Building {
	// GoodGay全局函数是Builing好朋友,可以访问
	friend void goodGay(Building* building);
public:
	Building() {
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom; //卧室
};
//全局函数
void goodGay(Building *building) {
	cout << "好基友正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友正在访问:" << building->m_BedRoom<< endl;
}

void test01() {
	Building building;
	goodGay(&building);
}

int main() {
	test01();
	system("pause");
	return 0;
}

4.4.2 类作友元

#include<iostream>
#include<string>
using namespace std;
//类作友元
class Building;
class GoodGay {
public:
	GoodGay();
	void visit();//参观函数访问Building属性
	Building* building;
};
class Building {
	//GoodGay类可以访问本类中私有成员
	friend class GoodGay;
public:
	Building();
public:
	string m_SittingRoom;
private:
	string m_BedRoom;
};
//类外写成员函数
Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
	//创建建筑物对象
	building = new Building;
}
void GoodGay::visit() {
	cout << building->m_SittingRoom << endl;
	cout << building->m_BedRoom << endl;
}
void test01() {
	GoodGay gg;
	gg.visit();
}

int main() {
	test01();
	system("pause");
	return 0;
}

4.4.3 成员函数作友元

operator+
#include<iostream>
#include<string>
using namespace std;
//类作友元
class Building;
class GoodGay {
public:
	GoodGay();
	void visit();//参观函数访问Building属性
	void visit2();//不可以访问私有成员
	Building* building;
};
class Building {
	// 告诉编译器GoodGay下visit作为奔雷好朋友,可以访问私有成员
	friend void GoodGay::visit();
public:
	Building();
public:
	string m_SittingRoom;
private:
	string m_BedRoom;
};
//类外写成员函数
Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
	//创建建筑物对象
	building = new Building;
}
void GoodGay::visit() {
	cout << building->m_SittingRoom << endl;
	cout << building->m_BedRoom << endl;
}
void GoodGay::visit2() {
	cout << building->m_SittingRoom << endl;
	/*cout << building->m_BedRoom << endl;*/
}
void test01() {
	GoodGay gg;
	gg.visit();
	gg.visit2();
}

int main() {
	test01();
	system("pause");
	return 0;
}

4.5 运算符重载

概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

4.5.1 加号运算符重载

#include<iostream>
using namespace std;
//加号运算符重载
class Person {
public:
	//1.成员函数重载+号
	/*Person operator+(Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}*/
	int m_A;
	int m_B;
};
//2.全局函数重载+号
Person operator+(Person& p1, Person& p2) {
	Person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
void test01() {
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	Person p3 = p1 + p2;
	cout << p3.m_A << endl;
	cout << p3.m_B << endl;
}



int main() {
	test01();
	system("pause");
	return 0;
}

总结1:对于内置的数据类型的表达式的运算符是不可能改变的
总结2:不要滥用运算符重载

4.5.2 左移运算符重载

作用:可以输出自定义数据类型

#include<iostream>
using namespace std;
//左移运算符重载
class Person {
public:
	//利用成员函数重载 左移运算符 一般不用,无法实现在左侧,利用全局函数实现
	void operator<<(Person &p) {

	}
	int m_A;
	int m_B;
};
ostream & operator<<(ostream &cout, Person &p) {
	cout << p.m_A << p.m_B << endl;
	return cout;
}

void test01() {
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	cout << p1 << endl;

}



int main() {
	test01();
	system("pause");
	return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义数据类型

4.6 继承

继承是面向对象三大特性之一

4.6.1 继承的基本语法

#include<iostream>
using namespace std;

//普通实现页面
//class Java {
//public:
//	void header() {
//		cout << "首页、公开课、登录、注册..(公共头部)" << endl;
//	}
//	void footer() {
//		cout << "帮助中心、交流合作、站内地图" << endl;
//	}
//	void left() {
//		cout << "java、Python、C++" << endl;
//	}
//	void content() {
//		cout << "java学科" << endl;
//	}
//};
//class Python {
//public:
//	void header() {
//		cout << "首页、公开课、登录、注册..(公共头部)" << endl;
//	}
//	void footer() {
//		cout << "帮助中心、交流合作、站内地图" << endl;
//	}
//	void left() {
//		cout << "java、Python、C++" << endl;
//	}
//	void content() {
//		cout << "Python学科" << endl;
//	}
//};
//class Cpp {
//public:
//	void header() {
//		cout << "首页、公开课、登录、注册..(公共头部)" << endl;
//	}
//	void footer() {
//		cout << "帮助中心、交流合作、站内地图" << endl;
//	}
//	void left() {
//		cout << "java、Python、C++" << endl;
//	}
//	void content() {
//		cout << "C++学科" << endl;
//	}
//};
//继承实现页面
//公共页面类
class BasePage {
	public:
	void header() {
		cout << "首页、公开课、登录、注册..(公共头部)" << endl;
	}
	void footer() {
		cout << "帮助中心、交流合作、站内地图" << endl;
	}
	void left() {
		cout << "java、Python、C++" << endl;
	}
};
//继承好处:减少重复代码
//语法:  class 子类 :继承方式  父类
//子类 也成为派生类
//父类 也成为基类
class Java :public BasePage {
public:
	void content() {
		cout << "java" << endl;
	}
};
class Python :public BasePage {
public:
	void content() {
		cout << "python" << endl;
	}
};
class Cpp :public BasePage {
public:
	void content() {
		cout << "cpp" << endl;
	}
};
void test01() {
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	Cpp cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();


}

int main() {
	test01();
	system("pause");
	return 0;
}

继承作用:可以减少重复代码
子类 也成为派生类
父类 也成为基类

4.6.2 继承方式

继承语法: class 子类:继承方式 父类

继承方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承
    在这里插入图片描述
#include<iostream>
using namespace std;
//继承方式

//公共继承
class Base1 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Base1 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son :public Base1 {
public:
	void func() {
		m_A = 10; //父类公共权限成员 到子类依然是公共权限
		m_B = 10;//父类中的保护权限成员,到子类中依然是保护权限
		//m_C = 10;//父类中私有成员 子类无法得到
	}
};
void test01() {
	Son s1;
	s1.m_A = 100;
	// s1.m_B = 100;//s1中m_B是保护权限 类外访问不到
}
// 保护继承
class Base2 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2 :protected Base2 {
public:
	void func() {
		m_A = 10; //父类公共权限成员 到子类依然是保护权限
		m_B = 10;//父类中的保护权限成员,到子类中依然是保护权限
		//m_C = 10;//父类中私有成员 子类无法得到
	}
};
void test02() {
	Son2 s2;
	//s2.m_A = 100; //在son2中m_A变成保护权限,因此类外访问不到
	//s2.m_B = 100; //在son2中m_B变成保护权限,因此类外访问不到
}
//私有继承
class Son3 :private Base2 {
public:
	void func() {
		m_A = 100;
		m_B = 100;
		//m_C=100; //父类中私有成员,子类访问不到
	}
};
void test03() {
	Son3 s3;
	//s3.m_A = 100; //在son2中m_A变成私有成员,因此类外访问不到
//s3.m_B = 100; //在son2中m_B变成私有成员,因此类外访问不到
}
class Grandson :public Son3 {
public:
	void func() {
		//m_A = 100; son3 m_A变为私有成员
	}
};
int main() {
	test01();
	system("pause");
	return 0;
}

4.6.3 继承中的对象模型

#include<iostream>
using namespace std;
//继承中的对象模型

class Base {
public:
	int m_A;
private:
	int m_B;
protected:
	int m_C;
};
class Son :public Base {
public:
	int m_D;
};
//利用开发人员命令提示工具查看对象模型
//查看命令 cl /d1 reportSingleClassLayout类名 文件名
void test01() {
	//父类中所有非静态成员属性都会被子类继承下去
	//父类中私有成员属性 是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
	cout << sizeof(Son) << endl;  // 16 
}
int main() {
	test01();
	system("pause");
	return 0;
}

4.6.4 继承中构造和析构顺序

#include<iostream>
using namespace std;
//继承构造和析构顺序

class Base {
public:
	Base() {
		cout << "Base构造函数" << endl;
	}
	~Base() {
		cout << "Base析构函数" << endl;
	}
};
class Son :public Base {
public:
	Son() {
		cout << "Son构造函数" << endl;
	}
	~Son() {
		cout << "Son析构函数" << endl;
	}
};
//Base构造函数
//Son构造函数
//Son析构函数
//Base析构函数
void test01() {
	/*Base b1;*/
	//继承中的构造和析构顺序如下:
	//先构造父类,再构造子类,析构顺序与构造顺序相反

	Son s1;
}
int main() {
	test01();
	system("pause");
	return 0;
}

总结:先构造父类,再构造子类,析构顺序与构造顺序相反

4.6.5 继承同名成员处理方式

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
//继承同名成员处理
class Base {
public:
	Base() {
		m_A = 100;
	}
	void func() {
		cout << "Base" << endl;
	}
	void func(int a) {
		cout << "Base"<<a << endl;
	}
	int m_A;
};
//同名成员属性调用
class Son :public Base {
public:
	Son() {
		m_A = 200;
	}
	void func() {
		cout << "Son" << endl;
	}
	int m_A;

};
//同名成员函数调用
void test01() {
	Son s;
	cout << s.m_A << endl;
	//如果通过子类对象 访问父类中同名成员,需要加作用域
	cout << s.Base::m_A << endl;
	s.func();
	s.Base::func();
	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
	//如果项访问到父类中被隐藏的同名成员函数,需要加作用域
	//s.func(100);
	s.Base::func(1);
}

int main() {
	test01();
	system("pause");
	return 0;
}

4.6.6 继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式

class Base {
public:
	static int m_A;
	static void func() {
		cout << "Base" << endl;
	}
};
int Base::m_A = 100;
class Son :public Base {
public:
	static int m_A;
	static void func() {
		cout << "Son" << endl;
	}
};
int Son::m_A = 200;
//同名静态成员属性
void test01() {
	//1.通过对象访问
	Son s;
	cout << s.m_A << endl;
	cout << s.Base::m_A << endl;
	//2.通过类名方式访问
	cout << Son::m_A << endl;
	//第一个::代表通过类名方式访问 第二个::代表访问父类作用域下
	cout << Son::Base::m_A << endl;
	//通过对象访问
	s.func();
	s.Base::func();
	//通过类名访问
	Son::func();
	Son::Base::func();
}
//同名静态成员函数
int main() {
	test01();
	system("pause");
	return 0;
}

4.6.7 多继承语法

c++允许一个类继承多个类
语法: class 子类:继承方式1 父类1,继承方式2 父类2
多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议多继承

#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式

class Base1 {
public:
	Base1() {
		m_A = 100;
	}
	 int m_A;
	
};
class Base2 {
public:
	Base2() {
		m_A = 200;
	}
	int m_A;

};
class Son :public Base1, public Base2 {
public:
	Son() {
		m_C = 300;
		m_D = 400;
	}
	int m_C;
	int m_D;
};


void test01() {
	Son s;
	cout << sizeof(s) << endl;
	//当父类中出现同名成员,需要加作用域区分
	//cout << s.m_A << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}
//同名静态成员函数
int main() {
	test01();
	system("pause");
	return 0;
}

总结:多继承中如果父类中出现了同名情况,子类使用时要加作用域

4.6.8菱形继承

菱形继承概念:

  • 两个派生类继承同一个基类
  • 又有某个类同时继承两个派生类
  • 这种继承被称为菱形继承或钻石继承
#include<iostream>
using namespace std;
//动物类
class Animal {
public:
	int m_Age;
};
//利用虚继承 解决菱形继承问题
// 继承之前 加上关键字 virtual 变为虚继承
// Animal类称为虚基类
//羊类
class Sheep:virtual public Animal{};
//驼类
class Tuo:virtual public Animal{};
//羊驼类
class Yangtuo :public Sheep, public Tuo{};


void test01() {
	Yangtuo st;
	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 29;
	//当菱形继承时,两个父类拥有相同数据,需要加以作用域区分
	cout << st.Sheep::m_Age << endl;
	cout << st.Tuo::m_Age << endl;
	
	//这份数据一个即可,菱形继承导致数据两份,资源浪费
	cout << st.m_Age << endl;
	cout << sizeof(st) << endl;

}
int main() {
	test01();
	system("pause");
	return 0;
}

4.7 多态

4.7.1 多态的基本概念

多态是C++面向对象三大特性之一
多态分为两类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include<iostream>
using namespace std;
//多态

//动物类
class Animal {
public:
	//虚函数
	virtual void speak() {
		cout << "动物在说话" << endl;
	}
};
class Cat :public Animal {
public:
	//重写 函数返回值类型 函数名 函数列表 要相同
	void speak() {
		cout << "小猫在说话" << endl;
	}
};
class Dog :public Animal {
public:
	void speak() {
		cout << "小狗在说话" << endl;
	}
};
//地址早绑定 在编译阶段确定函数地址
//如果执行猫说话,那么这个函数地址不能提前绑定,需要在运行阶段绑定,地址晚绑定

//动态多态满足条件
//1.有继承关系
//2.重写父类虚函数

//动态多态使用
//父类指针或者引用 指向与类对象

void doSpeak(Animal& animal) {  //Animal &animal=cat
	animal.speak();
}
void test01() {
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}
int main() {
	test01();
	system("pause");
	return 0;
}

4.7.2 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

语法: virtual 返回值类型 函数名 (参数列表)=0;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也为抽象类
#include<iostream>
using namespace std;
//纯虚函数和抽象类
class Base {
public:
	//纯虚函数
	//只要右一个纯虚函数,这个类称为抽象类
	//抽象类特点:
	//1.无法实例化对象
	//2.抽象类的子类 必须要重写父类中的纯虚函数 否则也属于抽象类
	virtual void func() = 0;

};
class Son :Base {
public:
	void func() {

	}
};


void test01() {
	//Base b;//抽象类是无法实例化对象
	//new Base;
	Son s; // 子类必须重写父类中纯虚函数
}

int main() {
	test01();

	system("pause");
	return 0;
}

4.7.3 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法: virtual ~类名(){}
纯虚析构语法:virtual ~类名()=0;

#include<iostream>
using namespace std;

//虚析构和纯虚析构
class Animal {
public:
	Animal() {
		cout << "Animal构造函数调用" << endl;
	}
	//利用虚析构可以解决父类指针释放子类对象时不干净的问题;
	/*virtual ~Animal() {
		cout << "Animal析构函数调用" << endl;
	}*/
	//纯虚析构 需要声明也需要实现
	//有了纯虚析构,也属于抽象类,无法实例对象
	virtual ~Animal() = 0;
	virtual void speak() = 0;
};
Animal:: ~Animal() {
	cout << "Animal纯虚函数调用" << endl;
}

class Cat :public Animal {
public:
	Cat(string name) {
		cout << "cat构造函数调用" << endl;
		m_Name=new string(name);
	}
	virtual void speak() {
		cout << *m_Name<<"小猫在说话" << endl;
	}
	~Cat() {
		if (m_Name != NULL) {
			cout << "cat析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
	string *m_Name;
};

void test01() {
	Animal* animal = new Cat("TOM");
	animal->speak();
	//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
	delete animal;
}

int main() {
	test01();
	system("pause");
	return 0;
}

5 文件操作

C++中对文件操作需要包含头文件

文件类型分为两种:

  • 1.文本文件 文件以文本的ASCII码形式存储在计算机中
  • 2.二进制文件 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类:

  • 1.ofstram:写操作
  • 2.ifstream:读操作
  • 3.fstram:读写操作

5.1 文本文件

5.1.1 写文件

写文件步骤如下:
1.包含头文件
#incluede
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open(“文件路径”,打开方式);
4.写数据
ofs<<“写入数据”
5.关闭文件

在这里插入图片描述
注意:文件打开方式可以配合使用,利用|操作符

#include<iostream>
#include<fstream> //包含头文件
using namespace std;

//文本文件 写文件
void test01() {
	//1.包含头文件 fstream

	//2.创建流对象
	ofstream ofs;

	//3.指定打开方式
	ofs.open("test.txt", ios::out);
	//4.写数据
	ofs << "张三" << endl;
	ofs << "男" << endl;
	//5.关闭文件
	ofs.close();
}

int main() {
	test01();

	system("pause");
	return 0;
}

5.1.2读文件

步骤类似,但是读取方式相对于比较多

读文件步骤如下:
1.包含头文件
#inclued
2.创建流对象
ifstream ifs;
3.打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式);
4.读数据
四种读取方式
5.关闭文件
ifs.close();

#include<iostream>
#include<fstream> //包含头文件
#include<string>
using namespace std;

//文本文件 读文件
void test01() {
   //1.包含头文件 fstream

   //2.创建流对象
   ifstream ifs;

   //3.打开文件并判断文件是否打开成功
   ifs.open("test.txt", ios::in);
   if (!ifs.is_open()) {
   	cout << "打开失败" << endl;
   	return;
   }
   //4.读数据
   //第一种
   /*char buf[1024] = { 0 };
   while (ifs >> buf) {
   	cout << buf << endl;
   }*/
   //第二种
   /*char buf[1024] = { 0 };
   while (ifs.getline(buf, sizeof(buf))) {
   	cout << buf << endl;
   }*/
   //第三种
   /*string buf;
   while (getline(ifs, buf) ){
   	cout << buf << endl;
   }*/
   //第四种
   char c;
   while ((c=ifs.get())!=EOF) { //EOF end of file
   	cout << c;
   }
   //5.关闭文件
   ifs.close();
}		

int main() {
   test01();

   system("pause");
   return 0;
}

5.2 二进制文件

以二进制方式对文件进行读写操作
打开方式要指定为ios::binary

5.2.1 写文件

二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数

#include<iostream>
#include<fstream> //包含头文件
#include<string>
using namespace std;
//二进制文件 写文件
class Person {
public:
	char m_Name[64]; //姓名

	int m_Age;//奈年龄
};
void test01() {
	//1.包含头文件
	//2.创建流对象
	ofstream ofs("person.txt", ios::out | ios::binary);
	//3.打开文件
	/*ofs.open("person.txt", ios::out | ios::binary);*/
	//4.写文件
	Person p = { "张三",18 };
	ofs.write((const char*)&p, sizeof(Person));
	//5.关闭文件
	ofs.close();
}
int main() {
	test01();

	system("pause");
	return 0;
}

5.2.2 读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存种一段存储空间,len是读写的字节数

#include<iostream>
#include<fstream> //包含头文件
#include<string>
using namespace std;
//二进制文件 读文件
class Person {
public:
	char m_Name[64]; //姓名

	int m_Age;//奈年龄
};
void test01() {
	//1.包含头文件
	//2.创建流对象
	ifstream ifs;
	//3.打开文件 判读是否打开成功
	ifs.open("person.txt", ios::out | ios::binary);
	if (!ifs.is_open()) {
		cout << "文件打开失败" << endl;
		return;
	}
	//4.写文件
	Person p;
	ifs.read((char *) & p,sizeof(Person));
	cout << p.m_Name << p.m_Age << endl;
	//5.关闭文件
	ifs.close();
}
		

int main() {
	test01();

	system("pause");
	return 0;
}
  • 10
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在C编程语言的基础知识面试中,通常会涉及以下几个方面的问题: 1. C语言的特点与用途:C语言是一种通用的高级程序设计语言,可以用来开发底层操作系统、嵌入式系统、游戏开发等应用。它具有简洁、效率高、可移植性强等特点。 2. C语言的数据类型与变量:C语言支持不同的数据类型,包括整型、浮点型、字符型等。使用变量来存储数据,变量需要先声明后使用,并且可以进行各种运算。 3. C语言的控制流程:C语言中的控制流程有条件判断语句、循环语句和跳转语句。条件判断语句通过判断条件的真假来决定执行哪个分支;循环语句可以重复执行一段代码,直到满足退出条件;跳转语句可以改变代码的执行顺序。 4. C语言的函数与库:C语言支持函数的定义和调用,可以将一段代码封装成函数,提高代码的复用性。此外,C语言还提供了一些标准库,包括输入输出库、数学库等,可以在程序中使用这些库函数来完成各种操作。 5. C语言的指针和内存管理:C语言具有灵活的指针操作功能,可以通过指针来直接访问内存中的数据。指针可以用于动态内存分配和释放,通过调用malloc()和free()函数来进行操作。 除了以上几个方面的基础知识,面试中可能还会涉及到关于C语言的编程题,例如要求解决某个具体的问题或者实现某个算法等。在面试中,不仅要掌握C语言的基础知识,还需要具备解决问题的思路和能力,能够独立分析和编写C程序。 ### 回答2: C基础知识面试主要涉及面向过程的编程语言C的基础概念、语法和应用。在面试中,通常会涉及以下几个方面的问题: 1. C语言的基本概念和特性:要求候选人能够解释C语言的起源、用途和特点,了解C语言的运行环境和编译过程。 2. C语言的数据类型:面试官可能会问到C语言中的基本数据类型,如int、char、float等,以及它们在内存中的存储方式和占用空间大小。 3. C语言的控制结构:掌握C语言中的分支语句(if-else、switch-case)和循环语句(for、while、do-while),并能够解答相关的应用题。 4. C语言的函数:了解函数的定义、声明和调用规则,理解函数的参数传递和返回值机制,能够编写简单的函数。 5. C语言的指针:熟悉指针的基本概念和用法,了解指针和数组、指针和函数之间的关系,能够解决指针相关的问题。 6. C语言的内存管理:了解动态内存分配(malloc、free)和静态内存分配(全局变量、局部变量)的区别和用法,了解内存泄漏和内存溢出的概念。 7. C语言的文件操作:熟悉文件的打开、读写、关闭等基本操作,能够读写文本文件和二进制文件,了解文件指针的概念和用法。 8. C语言的预处理器:了解预处理器的作用和常用指令(如#define、#include等),理解宏定义的概念和用法。 在C基础知识面试中,除了回答问题,面试官可能还会要求候选人编写简单的C代码,以检测其编程能力和解决问题的能力。所以,在准备面试时,应该复习C语言的基础概念、语法和应用,并进行代码练习,提高自己的实践能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值