【C++体系】C/C++要点复习

前言

为了准备秋招及春招,现阶段的任务是将所学过的知识进行复习,并串联起来。
首当其冲的就是编程语言的复习,C++自己平时写的也比较多,但仍有很多语法细节不太记得了,这里浅记一些平常容易忽略的要点

C++基础入门

一维数组数组名

一维数组名称的用途

  1. 可以统计整个数组在内存中的长度(单位:字节B)
  2. 可以获取数组在内存中的首地址

已知一个数组的数组名,求数组元素的个数,如下:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;
cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;
cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;

可以通过数组名获取到数组首地址:

cout << "数组首地址为: " << (int)arr << endl;
cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl;
cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;

这里首地址即为第一个元素的地址

指针

指针所占内存空间

所有指针类型在32位操作系统下是4个字节

cout << sizeof(int *) << endl; // 4
cout << sizeof(char *) << endl; // 4
cout << sizeof(float *) << endl; // 4
cout << sizeof(double *) << endl; // 4

空指针和野指针

空指针:指针变量指向内存中编号为0的空间

int * p = NULL;

用途:初始化指针变量
注意:空指针指向的内存是不可以访问的


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

int * p = (int *)0x1100;

//访问野指针报错 
cout << *p << endl;

两者共同点:都不能访问

const修饰指针

const修饰指针有三种情况:

  1. const修饰指针 — 常量指针 (const int * p)
    const修饰的是指针,指针指向可以改,指针指向的值不可以更改

  2. const修饰常量 — 指针常量 (int * const p)
    const修饰的是常量,指针指向不可以改,指针指向的值可以更改

  3. const即修饰指针,又修饰常量 (const int * const p)
    const既修饰指针又修饰常量,两者均不可修改

指针和数组

利用指针访问数组中的元素

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

int* p = arr;  //指向数组的指针

cout << "第一个元素: " << arr[0] << endl; // 1
cout << "指针访问第一个元素: " << *p << endl; // 1
cout << "指针访问第二个元素: " << *(p+1) << endl; // 2

for (int i = 0; i < 10; i++)
{
	//利用指针遍历数组
	cout << *p << endl;
	p++;
}

指针和函数

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

// 地址传递
void swap2(int * p1, int *p2) // 声明与定义
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
swap(&a, &b);  // 调用传参

如果是数组名作为函数参数的话,两种写法效果相同,因为数组名就代表了首元素的地址

void bubbleSort(int * arr, int len)  //int * arr 也可以写为int arr[]
{
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

结构体

结构体指针

通过指针访问结构体中的成员
利用操作符 -> 可以通过结构体指针访问结构体属性

student stu = { "张三",18,100};
student * p = &stu;
p->score = 80; //指针通过 -> 操作符可以访问成员
cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;

函数参数

结构体作函数参数时,如果不想修改主函数中的数据,用值传递,反之用地址传递

// 声明
void printStudent2(student *stu);
// 调用
printStudent2(&stu);

C++核心编程

内存4大分区

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

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的。特点共享只读
  • 全局区:存放全局变量、静态变量、常量,该区域的数据在程序结束后由操作系统释放
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放内存(new),若程序员不释放(delete),程序结束时由操作系统回收

new操作符

C++中利用new操作符在堆区开辟数据
利用new创建的数据,会返回该数据对应的类型的指针

int* a = new int(10); // 创建一个值为10变量

开辟数组

int* arr = new int[10];
delete[] arr;

引用&(指针常量)

作用:给变量起别名,本质上还是原来那个变量

  • 引用必须初始化
  • 引用在初始化后,不可以改变(易错)

int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int& c = a; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用,a的值也会改变

cout << "a = " << a << endl; // 20
cout << "b = " << b << endl; // 20
cout << "c = " << c << endl; // 20

由于c是a的别名,c在被重新赋值后,a也会随之改变
这个特点也印证了,引用本质上是一个指针常量,即指针指向的值可以修改,但是指针的指向不允许修改。

引用作函数参数

可以代替指针,实现函数内修改实参的操作

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

void mySwap03(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}
mySwap03(a, b);

函数重载

函数名可以相同,提高复用性

函数重载满足条件

  1. 同一个作用域下
  2. 函数名称相同
  3. 函数参数类型、个数、顺序至少要有一个不同

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

类与对象

构造/析构函数写法

类的定义

class Person {
public:
	Person() {
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	Person(int age) {
		cout << "有参构造函数!" << endl;
		mAge = age;
	}
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}
	//析构函数在释放内存之前调用
	~Person() {
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};

利用类创建对象

Person man1; // 无参构造
Person man2(100); // 有参构建
Person newman(man); // 拷贝构造函数

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的重复释放内存空间的问题

class Person {
public:
	Person() {
		cout << "无参构造函数!" << endl;
	}
	
	Person(int age, int height) {
		cout << "有参构造函数!" << endl;
		m_age = age;
		m_height = new int(height);  // 堆区开辟
	}
	
	//拷贝构造函数  
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;  // 非堆区开辟
		m_height = new int(*p.m_height);  // 堆区开辟

	}

	//析构函数
	~Person() {
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}
public:
	int m_age;
	int* m_height; // 堆区开辟
};

列表初始化

一种简化语法的操作

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) {}
private:
	int m_A;
	int m_B;
	int m_C;
};

静态成员变量

即可以通过对象名访问,也可以通过类名访问(类名::静态成员变量名)
静态成员变量特点

  1. 在编译阶段分配内存
  2. 类内声明,类外初始化
  3. 所有对象共享同一份数据

定义与初始化:

class Person
{
public:
	static int m_A; //静态成员变量
private:
	static int m_B; // 如果设为私有,类外也无法直接访问
};
int Person::m_A = 10; // 初始化
int Person::m_B = 10;

Person p1;
p1.m_A; // 创建对象,利用对象名调用
Person::m_A; // 直接通过类名调用

this指针(重要)

this指针在Java中也存在,不过用法上还是略有区别

this指针指向被调用的成员函数所属的对象

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
	Person(int age)
	{
		//1、当形参和成员变量同名时,可用this指针来区分
		this->age = age;
	}

	Person& PersonAddPerson(Person p)
	{
		this->age += p.age;
		//返回对象本身
		return *this;
	}

	int age;
};

// 因返回的是对象本身,所以可以连续调用
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);

友元

友元分为三种:

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

第一种最简单,直接声明前加上friend关键字,放在类的最前头即可

friend void goodGay(Building * building);

第二种需要考虑到类定义的顺序
被友元访问的那个类,需要事先声明,顺序如下:

class B;

class A
{
public:
...
private:
	B *b;
};

class B
{
	friend class A;
};

第三种成员函数作友元,与类友元类似,不过只有类中特定的一个成员函数可以访问。总体形式一样,区别在于声明语句

friend void goodGay::visit();

运算符重载

不能重载的运算符有5种,分别是.->sizeof?:::

加号
class Person {
public:
	Person() {};
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	//成员函数实现 + 号运算符重载
	Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}

public:
	int m_A;
	int m_B;
};

//全局函数运算符重载 可以发生函数重载 
Person operator+(const Person& p2, int val)  
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

使用方法

Person p1(10, 10);
Person p2(20, 20);
Person p3 = p2 + p1;
Person p4 = p3 + 10;
左移

只能在全局函数中实现

//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

并且要作为友元,放在原类中

friend ostream& operator<<(ostream& out, Person& p);

继承

基本语法

class A : public B;

A 类称为子类 或 派生类
B 类称为父类 或 基类

class Base1
{
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

//公共继承
class Son1 :public Base1
{
public:
	void func()
	{
		m_A; //可访问 public权限
		m_B; //可访问 protected权限
		//m_C; //不可访问
	}
};

成员函数变量同名

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
s.m_A; // 子类的变量
s.Base::m_A; // 父类的变量,加类名访问
s.func(); // 子类的函数
s.Base::func(); // 父类的函数,加类名访问

多态

父类指针或引用指向子类对象

  • 静态多态: 函数重载和运算符重载属于静态多态,复用函数名——编译阶段确定函数地址
  • 动态多态: 派生类和虚函数实现运行时多态——运行阶段确定函数地址

父类定义一个虚函数,子类通过重写虚函数实现多态

// 父类
virtual void speak() {
		...
}

计算器

创建一个计算器父类与加法子类,并重写虚函数

//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public :

	virtual int getResult()
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

使用方法:

//创建加法计算器
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;  //用完了记得销毁

纯虚函数与抽象类

这个点也是比较陌生的点,平常很少用到不涉及,且和Java有一定程度上的区别

考虑到父类一般是抽象类,所编写的纯虚函数可能并不存在对应的实现,因此就要把它定义为纯虚函数

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

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

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
  • 类中只要有一个纯虚函数就称为抽象类
class Base
{
public:
	virtual void func() = 0;
};

class Son :public Base
{
public:
	void func()
	{
		cout << "func调用" << endl;
	};
};

虚析构与纯虚析构

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

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

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象(作用),提醒子类必须重写自己的析构函数
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

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

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;

	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal 纯虚析构函数调用!" << endl;
}

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

未完待续……

参考资料

  1. 黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难
  2. 《C++ Primer》
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeSlogan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值