c++核心编程

c++核心编程,第1张

重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!

本章是继上篇c++基础之后的c++核心。

生命就像一朵花,要拼尽全力绽放!

系列文章列表:

c++编程基础_森林女神的博客-CSDN博客_c++项目总结

1 程序内存分区模型

c++程序内存分区:

区域存放内容管理方式
代码区存放代码的二进制代码由 *** 作系统进行管理
全局区存放全局变量、静态变量、常量由 *** 作系统进行管理,该区域的数据在程序结束后由 *** 作系统释放。

栈区

函数的参数、局部变量等由编译器自动对内存分配和释放
堆区利用new *** 作符开辟内存由程序员分配和释放。若程序员不主动释放,程序结束由 *** 作系统释放

四区存在意义:

     不同区域存放的数据,赋予不同生命周期,使编程更灵活。

1.1 程序运行前

在程序编译后会生成可执行程序,在未执行该程序前,分为两个区域:

代码区:

        存放CPU执行的机器指令

        代码区有两个特点:

                是共享的。目的是对于可能被多次执行的程序,只需要在内存中有一份代码即可。

                是只读的。原因是防止程序被意外修改。

全局区:

        存放全局变量、静态变量

        还包含一些常量:字符串常量、const修饰的全局常量。(const 修饰的局部常量不在该区)

       

1.2 程序运行后

栈区

函数的参数、局部变量等由编译器自动对内存分配和释放
堆区利用new *** 作符开辟内存由程序员分配和释放。若程序员不主动释放,程序结束由 *** 作系统释放

注意事项:

        不要返回局部变量的地址,因为栈区的数据由系统自动释放,使用了可能会出错。

栈区示例:

#include 
#include 

using namespace std;

int* test() {
	// 局部变量 存放栈区,在函数执行完后系统自动释放
	int a = 10;
	// 返回局部变量地址
	return &a;
}

int main()
{
	int* p = test();
	cout << *p << endl;  // 第一次打印正确,因为编译器做了保留
	cout << *p << endl;  //第二次使用数据就不再保留了 
	cout << *p << endl;

	system("pause");
	return 0;
}

堆区示例:

#include 
#include 

using namespace std;

int* test() {
	// 利用new关键字,可以将数据开辟到堆区
	// 指针本质也是局部变量,放在栈上,但指针保存的数据是堆区地址
	int* p = new int(10);
	return p;
}

int main()
{
	int* p = test();
	// 三次打印都是正确的
	cout << *p << endl;  
	cout << *p << endl; 
	cout << *p << endl;

	system("pause");
	return 0;
}
1.3 new *** 作符

作用:在堆区开辟数据

特点:

  • 只能在堆区开辟数据;
  • 由程序员手动开辟,手动释放;
  • 释放利用 *** 作符delete;

语法:

        new 数据类型

利用new创建的数据,返回的是数据类型的指针。

示例1:基本语法

#include 
#include 

using namespace std;

int* test() {
	int* p = new int(10);
	return p;
}

int main()
{
	int* p = test();
	// 三次打印都是正确的
	cout << *p << endl;  
	cout << *p << endl; 
	cout << *p << endl;

	delete p;
	// cout << *p << endl; // 内存已被delete释放,再次访问就是非法操作,报错

	system("pause");
	return 0;
}

示例2:new 开辟数组

#include 
#include 

using namespace std;

//在堆区利用new开辟数组
void test() {
	// 创建10个元素的整型数组
	int* arr = new int[10];
	for (int i = 0; i < 10; i ++) {
		arr[i] = i;
	}

	for (int i = 0; i < 10; i++) {
		cout << arr[i]<

2 c++引用 2.1 引用的基本使用

作用:给变量起别名。可以跟变量一样 *** 作数据。

语法:数据类型& 别名 = 原名

示例:

#include 
#include 

using namespace std;

int main()
{
	int a = 10;
	int& b = a;  // 引用
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	// 从地址打印可以看出a和b指向的都是同一块内存
	cout << "a的地址为:" << &a << endl;
	cout << "b的地址为: " << &b << endl;

	system("pause");
	return 0;
}

2.2 引用注意事项

1、引用在定义时必须初始化。

        例如 int& b;//是错误

2、引用一旦初始化后,就不可以更改,即不能修改为其他变量的别名。

示例:

#include 
#include 

using namespace std;

int main()
{
	int a = 10;
	int b = 20;

	//int& c; // 错误,引用必须在定义时初始化
	int& c = a;// 一旦初始化后,就不可以更改
	c = b;// 这是赋值 *** 作,不是更改引用

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

	// 从地址打印可以看出a和c指向的都是同一块内存,没有更改c的引用
	cout << "a的地址为:" << &a << endl;
	cout << "b的地址为: " << &b << endl;
	cout << "c的地址为:" << &c << endl;
	

	system("pause");
	return 0;
}

2.3 引用做函数参数

 作用:函数传参数时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

示例:

#include 
#include 

using namespace std;
// 1 值传递 
void swap01(int a,int b) {
	int tmp = a;
	a = b;
	b = tmp;
}

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

// 3  引用传递
void swap03(int& a, int& b) {
	int tmp = a;
	a = b;
	b = tmp;
}

void testSwap01() {
	cout << "testSwap01:" << endl;
	int a = 10;
	int b = 20;

	swap01(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

void testSwap02() {
	cout << "testSwap02:" << endl;
	int a = 10;
	int b = 20;

	swap02(&a, &b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

void testSwap03() {
	cout << "testSwap03:" << endl;
	int a = 10;
	int b = 20;

	swap03(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

int main()
{
	testSwap01();
	testSwap02();
	testSwap03();

	system("pause");
	return 0;
}

总结:通过引用传参和按地址传参可以得到一样的效果,但是引用传参更简单方便。

2.4 引用做函数返回值

作用:引用可以作为函数的返回值

注意:

  • 不要返回局部变量的引用
  • 若函数返回的是引用,则函数调用可以作为左值

示例:

#include 
#include 

using namespace std;

// 函数返回引用
int& test() {
	static int a = 10;// 全局变量 在程序结束后才由系统释放
	return a;
}

int main()
{
	int& ref = test();
	cout << "ref = " << ref << endl;
	cout << "ref = " << ref << endl;
    
    // 函数返回的是引用,函数调用可以作为左值
	test() = 2000;
	cout << "ref = " << ref << endl;
	cout << "ref = " << ref << endl;

	system("pause");
	return 0;
}

结果:

ref = 10

ref = 10

ref = 2000

ref = 2000

2.5 引用的本质

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

#include 
#include 

using namespace std;

// 内部发现参数是引用,自动转换为 int* const x = &a;
void test(int& x) {
	x = 100; // ref是引用,自动转换为*x = 100;
}

int main()
{
	int a = 10;

	// 自动转换为指针常量 int* const ref = &a; 
    // 指针常量是指针指向不可以更改,也说明为什么引用不可以更改
	int& ref = a;
	// 内部发现ref是引用,编译器自动转换为:*ref = 20
	ref = 20;

	cout << "a = " << a << endl;
	cout << "ref = " << ref << endl;

	test(a);

	system("pause");
	return 0;
}

总结:c++推荐用引用,因为引用的本质是指针常量,但比使用指针方便,而且所有的指针 *** 作编译器都自动在内部转换做了。

2.6 常量引用

作用:若不想在函数内部改变传入实参的值,用常量引用来做函数参数,防止函数内部误 *** 作。

语法:const 数据类型 形参名

        在函数形参列表中,可以加const修饰形参,防止形参改变实参。

示例:

#include 
#include 

using namespace std;

// 形参用const修饰,防止内部误 *** 作改了实参
void showValue(const int& x) {
	// x = 100; // 错误,在函数内部不可以修改实参的值
	cout << "showValue :" << x << endl;
}

int main()
{
	int a = 10;
	showValue(a);
	cout << "a = " << a << endl;

	// int& ref = 10; // 引用本身需要一个合法的内存空间,不能直接给引用赋值数值
	
	// 用const修饰就可以,原因:编译器优化代码为:int tmp =10; const int& ref = tmp; 
	// ref是tmp的别名,只是tmp这个原名我们不知道不可以使用。
	const int& ref = 10;

	system("pause");
	return 0;
}

3 函数高级 3.1 函数默认参数

作用:函数的形参列表中形参可以有默认值,在函数调用时可传入实参或不传入。

语法:返回值类型 函数名(形参 =  默认值){}

注意事项:

  1. 如果参数列表某个位置有默认值,则从这个位置后的参数都必须有默认值
  2. 函数声明和函数实现只能有一个地方有默认值,不能同时出现默认值。

示例:

#include 
#include 

using namespace std;

int func(int a, int b, int c) {
	return a + b + c;
}

// 正确案例
int func2(int a, int b=20, int c=30) {
	return a + b + c;
}

// 错误案例
// 参数有默认值后,从这个位置后的参数都必须有默认值
//int func3(int a, int b = 20, int c) {
//	return a + b + c;
//}

// 错误案例 声明和实现都有默认值
//int func4(int a, int b = 20, int c = 30);
//int func4(int a, int b = 20, int c = 30) {
//	return a + b + c;
//}

int main()
{
	cout << func(10, 20, 30) << endl;
	cout << func2(10) << endl;

	system("pause");
	return 0;
}

3.2 函数占位参数

函数列表中可以有占位参数,用来做占位,调用函数时比如传入该位置的参数。

语法:返回值类型 函数名(数据类型){ }

示例:

#include 
#include 

using namespace std;

void func(int a, int) {
	cout << "print func";
}

// 占位参数也可以给默认值
int func2(int a, int = 20) {
	cout << "print func2";
}

int main()
{
	// func(10); //  错误,必须传入占位参数位置的参数值
	func(10, 20);

	func2(10);

	system("pause");
	return 0;
}

3.3 函数重载 3.3.1 函数重载概述

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

函数重载的条件:

  1. 在同一个作用域下;
  2. 函数名相同;
  3. 函数参数类型不同,或参数个数不同,或参数顺序不同。

注意:函数返回值不同不能函数重载。

示例:

#include 
#include 

using namespace std;

void func() {
	cout << "print func" << endl;
}

void func(int a) {
	cout << "print func(int a)" << endl;
}

void func(double a) {
	cout << "print func(double a)" << endl;
}

void func(int a,double b) {
	cout << "print func (int a,double b)" << endl;
}

void func(double a, int b) {
	cout << "print func (double a, int b)" << endl;
}

// 函数返回值不同不能函数重载 会报错
//int func(double a, int b) {
//	cout << "print func (double a, int b)" << endl;
//	return 0;
//}

int main()
{
	func();
	func(1);
	func(1.23);
	func(1,1.2);
	func(1.2,1);

	system("pause");
	return 0;
}

3.3.2 函数重载注意事项

注意两点:

  • 引用参数作为重载条件。有const和无const引用参数仍然可以重载,调用方式不同。
  • 函数重载碰到默认参数,容易出错,尽量避免重载时使用默认参数。

示例:

#include 
#include 

using namespace std;
// 1 引用作为重载条件
void func(int& a) {
	cout << "print func(int& a)" << endl;
}

void func(const int& a) {
	cout << "print func(const int& a)" << endl;
}

// 2 默认参数构成重载条件
void func2(int a, int b = 10) {
	cout << "print func2(int a,int b)" << endl;
}

void func2(int a) {
	cout << "print func2(int a)" << endl;
}


int main()
{
	int a = 10;
	func(a);// 调用无const
	func(1);// 调用有const
	
	//func2(1);// 碰到默认参数产生歧义不知道调哪个函数,需要避免
	
	func2(1, 10);

	system("pause");
	return 0;
}

4 类和对象

面向对象三大特性:

  • 封装
  • 继承
  • 多态

c++认为万事万物皆为对象,每个对象都有属性和行为。

例如:

人可以作为对象,属性有姓名、年龄、身高、体重……,行为有走、跑、吃饭、睡觉……

车可以作为对象,属性有方向盘、轮胎、座椅……,行为有载人、放音乐、开空调……

具有相同性质的对象,可以抽象称为类,人属于人类,车属于车类。

4.1 封装 4.1.1 封装的意义

 封装的意义:

  1. 将属性和行为作为一个整体,表现事物
  2. 将属性和行为加以权限控制

意义1:将属性和行为作为一个整体,表现事物

语法:

        class 类名{

                访问权限:

                        属性

                        行为

        }

扩展:

  • 类中的属性和行为,统一称为 成员
  • 属性,又可称为成员属性,或成员变量
  • 行为,又可称为成员函数,或成员方法

示例:

#include 
#include 

using namespace std;

const double PI = 3.14;

class Circle {
	// 访问权限
public:
	// 属性:
	// 半径
	int m_r;

	// 行为:
	// 圆的周长
	double getZC() {
		return 2 * PI * m_r;
	}
};

int main()
{
	// 通过圆类 创建具体的圆对象
	// 实例化(通过一个类创建一个对象的过程)
	Circle c1;
	// 给圆对象的属性赋值
	c1.m_r = 10;

	// 获取对象的行为
	cout << c1.getZC() << endl;

	system("pause");
	return 0;
}

意义2:将属性和行为加以权限控制

访问权限有三种:

权限名作用域
public 公共权限类内可以访问,类外可以访问
protected 保护权限类内可以访问,类外不可以访问。子类可以访问父类成员
private 私有权限类内可以访问 类外不可以访问。子类不可以访问父类成员

示例:

#include 
#include 

using namespace std;

const double PI = 3.14;

class Person {
	// 访问权限
public:
	// 属性:
	// 姓名
	string m_name;

protected:
	// 汽车
	string m_car;

private:
	// yhk密码
	int m_password;

	// 行为:
private:	
	double func() {
		m_name = "张三";
		m_car = "奔驰";
		m_password = 1234567;
	}
};

int main()
{
	Person p;
	p.m_name = "echo";
	//p.m_car = "宝马";//不能访问
	//p.m_password = 123;//不能访问

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

struct和class唯一的区别在于默认访问权限不同。

区别:

  • struct 默认权限:公共
  • class 默认权限:私有

示例:

#include 
#include 

using namespace std;

const double PI = 3.14;

class Test1 {
	int a;// 默认权限私有
};

struct Test2 {
	int a;// 默认权限公共
};

int main()
{
	Test1 t1;
	// t1.a = 10;// 报错

	Test2 t2;
	t2.a = 10;

	system("pause");
	return 0;
}

4.1.3 成员属性设置为私有

优点:

  1. 将所有成员属性私有化,自己控制读写权限;
  2. 对于写权限,可以检测数据写入的有效性。

示例:

#include 
#include 

using namespace std;

const double PI = 3.14;

class Men {
public:
	void setName(string name) {
		m_name = name;
	}

	string getName() {
		return m_name;
	}

	void setAge(int age) {
		if (age < 0 || age>150) {
			cout << "你输入的年龄错误" << endl;
			m_age = 0;
		}
		else {
			m_age = age;

		}
	}

	int getAge() {
		return m_age;
	}

	void setLover(string lover) {
		m_lover = lover;
	}

private:
	string m_name;
	int m_age;
	string m_lover;
};


int main()
{

	Men m;
	m.setName("张三");
	cout << m.getName() << endl;

	m.setAge(1000);
	cout << m.getAge() << endl;

	m.setLover("rose");
	//cout << m.getLover() << endl;  // 没有获取lover的函数
	//cout << m.m_lover << endl;  // 没有获取lover的权限


	system("pause");
	return 0;
}

4.2 对象的初始化和清理 4.2.1 构造函数和析构函数

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

  • 一个对象或变量没有初始化,对其使用的后果是未知的;
  • 使用完一个对象或变量,没有及时清理,也可能会造成安全问题。

c++利用构造函数和析构函数解决上述问题。

这两个函数在类使用过程中会被编译器自动调用,完成对象初始化和清理工作。

如果代码不提供构造和析构,编译器会提供默认的空的构造函数和析构函数并调用。

构造函数:主要用于创建对象时为成员属性赋值,构造函数由编译器自动调用,无须手动调用。

析构函数:主要用于对象销毁前系统自动调用,执行一些清理工作。

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

特点:

  1. 没有返回值,也不用写void;
  2. 函数名与类名相同;
  3. 构造函数也可以有参数,可以有重载;
  4. 程序在调用对象时,一定会自动调用调用一次构造;

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

特点:

  1. 没有返回值,也不用写void;
  2. 函数名与类名相同,在函数名前有一个~
  3. 析构函数不可以有参数,不能重载;
  4. 程序在对象销毁前会,一定会自动调用一次析构函数

示例:

#include 
#include 

using namespace std;

class Person {
public:
	Person() {
		cout << "这是构造函数" << endl;
	}

	~Person() {
		cout << "这是析构函数" << endl;

	}
};

void test() {
	Person p;
}

int main()
{
	test();

	system("pause");
	return 0;
}

结果:

这是构造函数

这是析构函数

4.2.2 构造函数的分类和调用

 

示例:
 

#include 
#include 

using namespace std;

class Person {
public:
	// 无参构造 也是普通构造
	Person() {
		cout << "这是构造函数 无参" << endl;
	}

	// 有参构造  也是普通构造
	Person(int a) {
		cout << "这是构造函数 有参" << endl;
	}

	// 拷贝构造
	Person(const Person& p) {
		cout << "这是构造函数 拷贝" << endl;
		name = p.name;
	}

	~Person() {
		cout << "这是析构函数" << endl;

	}

	string name;
};

//调用法
// 1 括号法
void test() {
	
	Person p1;
	Person p2(10);
	Person p3(p2);
	// 注意事项 调用默认构造函数时,不要加括号
	// 因为编译器会认为是一个函数声明,不会认为在创建对象
	// Person p4();//无法达到创建对象效果
}

// 2 显示法
void test2() {
	Person p21;
	Person p22 = Person(10);
	Person p23 = Person(p22);

	// Person(10);//是匿名对象 特点:当前结束后,系统会立即回收匿名对象 在当前c++14及后续版本不支持该种写法了
	// cout << "aaaa" << endl;

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

// 3 隐式转换法
void test3() {
	Person p31 = 10;// 相当于写了  Person p31=Person(10); 有参构造
	Person p32 = p31;// 拷贝构造
}


int main()
{
	//test();
	//test2();
	test3();

	system("pause");
	return 0;
}

4.2.3 拷贝构造函数调用时机

4.2.4 构造函数调用规则

4.2.5 深拷贝和浅拷贝

4.2.6 初始化列表

4.2.7 类对象作为类成员

4.2.8 静态成员

4.3 对象模型和this指针

4.4 友元

4.5 运算符重载 4.6 继承 4.7 多态

5 c++文件 *** 作

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/langs/2889687.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-09-14
下一篇2022-09-14

发表评论

登录后才能评论

评论列表(0条)