C++核心编程

2023-05-16

C++核心编程

1、c++内存模型

c++程序执行时将内存大致分为4个区域

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

c++内存模型图如下:
在这里插入图片描述

代码区特点

共享的:对于频繁执行的程序,在内存中只要有一份代码即可

只读的:防止系统意外修改它的指令

全局区特点

该区域的数据,在程序结束后由操作系统释放

栈区特点

由编译器自动编译释放,存放函数的参数值,局部变量等

注意事项:栈区编译的数据执行完后由编译器自动释放,不要返回局部变量的地址

堆区特点

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在c++中主要通过new关键字在堆区开辟内存

new 和delete关键字

new关键字开辟内存

elete关键字释放指针p指向的内存

int* func()
{
	//用new关键字开辟内存,new返回的是该数据的指针
	int * p = new int(21);
	return p;
}

int main()
{
	int * p = func();
	cout << *p << endl;
	//使用delete关键字释放指针p指向的内存
	delete p;
	system("pause");
	return 0;
}

2、引用

引用可以看做是变量的一个别名,通过这个别名和原来的名字都能够找到这个变量。

语法:

 //数据类型  &别名 = 原名
 int a = 10;
 int &b = a;

注意事项:引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它变量。

引用作为函数参数

将函数的形参指定为引用的形式,这样在调用函数时就会将实参和形参绑定在一起,让它们都指代同一个变量。如此一来,如果在函数体中修改了形参的值,那么实参的值也会被修改。

//引用传递,&a1,&b1分别是变量a和b的引用
void swap(int &a1,int &b1)
{
	int temp = a1;
	a1 = b1;
	b1 = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << a << "\n" << b << endl;
	system("pause");
	return 0;
}

引用作为函数返回值

不能返回局部数据(例如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在了,C++ 编译器检测到该行为时也会给出警告。

int& test() {
	static int a = 10;//这里把a变成静态变量,存放在全局区,全局区的数据在程序结束后由系统释放
	return a;
}
int main()
{
	int &ref = test();
	cout << ref << endl;  //10
    test() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值
    cout << ref << endl;  //1000
	system("pause");
	return 0;
}

引用的本质

引用的本质是一个指针常量,所有指针操作由编译器完成。

常量引用

常量引用主要用来修饰形参,防止误操作;

在函数中可以用const修饰形参,防止形参改变实参

void showValue(const int& a1) {
	// a1+=1000; 不允许修改
    cout << a1 << endl;
}

int main()
{
	int a = 10;
	showValue(a);
	system("pause");
	return 0;
}

3、函数高级

函数默认参数

在c++中函数的形参列表中的参数是可以有默认值的;

在函数中如果某个参数有默认值,那么它后面的所有参数都得有默认参数;

如果函数声明有默认参数,那么函数的定义就不能有默认参数

//如果我们有传入数据,就用传入的数据,没有的话就用默认数据
int func01(int a, int b = 10, int c = 10) {
	return a + b + c;
}

//函数声明,存在默认参数
int func02(int a, int b = 10);
//那么在函数定义时就不能有默认参数
int func02(int a, int b) {
	return a + b;
}

int main()
{
	cout << func01(1, 4) << endl;//15
	cout << func01(1) << endl;  //21
	cout << func02(1) << endl; //11
	system("pause");
	return 0;
}

函数占位参数

c++中函数的形参列表可以有占位参数,用来占位,调用函数时必须填补该位置

占位参数还可以有默认值

//占位参数,设置了默认值
void func(int a, int = 5) {
	cout << "This is a function " << endl;
	cout << a << endl;
}

int main() {
    //调用函数时填补占位参数
	func(12, 9);
	system("pause");
	return 0;
}

函数重载

重载:函数名称相同,函数参数类型不同或者个数不同或者顺序不同

void func() {
	cout << "This is a function  01 " << endl;
}

void func(int a) {
	cout << "This is a function  02" << endl;
}
void func(double a) {
	cout << "This is a function  03" << endl;
}

void func(int a,double b) {
	cout << "This is a function  04" << endl;
}
void func(double a, int b) {
	cout << "This is a function  05" << endl;
}

注意事项:

  • 引用可以作为重载条件
  • 重载时避免出现默认参数,会出现二义性

4、面向对象

1、封装

封装

将属性和行为作为一个整体,表现生活中的事务

将属性和行为加以权限控制

成员变量和成员函数

class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};

成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。

成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。

权限

类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。

C++ 中的 public、private、protected 只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分。

在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

struct 和 class 的区别

区别:

  • 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的

  • class 继承默认是 private 继承,而 struct 继承默认是 public 继承

  • class 可以使用模板,而 struct 不能

属性私有

优点:

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

2、对象初始化和清理

构造函数

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

class Person {
public:
	int m_age;
	Person() {
		cout << "Person的无参构造" << endl;
	}
	Person(string name,int age) {
		 m_age = age;
		cout << "Person的有参构造" << endl;
	}
    	Person(const Person &p) {
		 m_age = p.m_age;
		cout << "Person的拷贝构造" << endl;
	}

};

析构函数

主要作用在于对象销毁前由系统自动调用,执行一些清理工作,

程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次

class Person {
public:
	int m_age;
	~Person() {
		cout << "Person的析构函数" << endl;
	}
};

深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝

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

浅拷贝带来的问题:堆区的内存重复释放

深拷贝解决:自己提供拷贝构造函数,给拷贝的副本重新开辟内存空间

Person(const Person &p) {
		m_age = p.m_age;
		m_Height = new int(*p.m_Height);
	}

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

构造和析构函数的调用

#include<iostream>
using namespace std;

//分类
class Person {
public:
	int m_age;
	Person() {
		cout << "Person的无参构造" << endl;
	}
	Person(int age) {
		m_age = age;
		cout << "Person的有参构造" << endl;
	}
	Person(const Person& p) {
		m_age = p.m_age;
		cout << "Person的拷贝构造" << endl;
	}
	~Person() {
		cout << "Person的析构函数" << endl;
	}
};
//调用
void test01() {
	//1.括号法
	Person p1;
	Person p2(30);
	Person p3(p2);//调用拷贝构造

	cout << "p2的年龄" << p2.m_age << endl;
	cout << "p3的年龄" << p3.m_age << endl;

	//2.显示法
	Person p4;
	Person p5 = Person(10);
	Person p6 = Person(p5);

	cout << "p5的年龄" << p5.m_age << endl;
	cout << "p6的年龄" << p6.m_age << endl;
	Person(10);//匿名对象 当前行执行结束,系统会立即回收匿名对象,不要用拷贝函数初始化匿名对象


	//3.隐式转换法
	//等价于 Person p7 = Person(p6)
	Person p7 = p6;
	//等价于 Person p8 = Person(10)
	Person p8 = 10;
}

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

拷贝函数使用场景

1、使用一个已创建的对象来初始化一个新的对象

Person p1;
Person p3(p1);

2、值传递的方式来给函数参数传值

//形参是实参拷贝的副本
void doWork(Person p) {
	
}
void test02() {

	Person p1;
    //值传递的方式来给函数参数传值
	doWork(p1);
}

3、以值方式返回局部对象

//返回的对象是局部对象拷贝的副本
Person doWork2() {
	Person p2;
	return p2;
}

void test03() {
	Person p3 = doWork2();
}

构造函数调用规则

默认情况下c++构造器至少给一个类添加3个函数

  • 默认构造函数(无参,函数体为空)

  • 默认析构函数(无参,函数体为空)

  • 默认拷贝构造函数,对属性进行值拷贝

构造函数的调用规则如下:

  • 如果用户定义有参构造,c++不再提供默认无参构造,但还是会提供默认拷贝构造函数
  • 如果用户提供拷贝构造函数,c++不会再提供其他构造函数

初始化列表

初始化列表:用来初始化属性

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	int height;
	int weight;

	//传统初始化属性
	/*P(int a, int b, int c){
	   m_A = a;
	   m_B = b;
	   m_C = c;
	}*/
    
	//初始化列表
	Person(int a, int b, int c) :age(a), height(b), weight(c) 
    {
	}
};

void test01() {
	Person p(18, 180, 130);
	cout << p.age << endl;
	cout << p.height << endl;
	cout << p.weight << endl;
}

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

类对象作为类成员

C++类中的成员允许是另一个类的对象,我们称该成员为:对象成员

当其他类的对象作为本类的成员:

  • 在构造中,是先构造其他类的,在构造本类的。
  • 在析构中,是先析构本类,再析构他类。

静态成员

静态成员变量:

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

静态成员函数:

  1. 所有对象共享同一个函数
  2. 静态成员函数只能访问静态成员函数
  3. 静态成员函数可以访问静态成员变量,不能访问非静态成员变量

静态成员访问方式:

  1. 通过对象进行访问
  2. 通过类名进行访问

3、c++对象模型和this指针

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

在c++中类内的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象

#include<iostream>
using namespace std;

//空对象
class Person {

};
class Demo {
	int m_A;//非静态成员变量属于对象上
};
class Student {
	//非静态成员函数不在对象上
	void sayHello() {

	}
};

class Work {
	//静态变量不在对象上
	static int a;
	static void sayHello() {
		cout << "你好呀" << endl;
	}
};
//类外初始化静态变量
int Work::a = 10;
void test01() {
	Person p;
	//在c++中,为了区别不同的空对象,编译器为其赋了一个字节的存储空间
	cout << sizeof(p) << endl;//1  空对象

	Demo d;
	cout << sizeof(d) << endl;//4   非静态成员变量属于对象上

	Student s;//空对象
	cout << sizeof(s) << endl;//1   非静态成员函数不在对象上

	Work w;//空对象
	cout << sizeof(w) << endl;//1  静态变量不在对象上
}
int main() {
	test01();
	system("pause");
	return 0;
}

this指针

this 是 C++中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	Person(int age) {
		//this指向成员变量或成员函数被调用的对象
		this->age = age;
	}
	//如果返回的是一个引用,则返回的是本身
	Person& personAddAge(Person& person) {
		this->age += person.age;
		//this指向p2的指针,而*this指向p2这个对象本体
		return *this;
	}
	//如果返回的是一个对象,则相当于创建了一个新的对象
	Person personAddAge1(Person& person) {
		this->age += person.age;
		return *this;
	}
};

void test01() {
	Person p(18);
	cout << "p的年龄为" << p.age << endl;
}

void test02() {
	Person p1(10);
	Person p2(10);
	Person p3(10);
	p2.personAddAge(p1).personAddAge(p1).personAddAge(p1);
	p3.personAddAge1(p1).personAddAge1(p1).personAddAge1(p1);
	cout << "p2的年龄为:" << p2.age << endl;//40
	cout << "p3的年龄为:" << p3.age << endl;//20
}
int main() {

	test01();//18
	test02();
	system("pause");
	return 0;
}

注意事项:

  • this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
  • this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
  • 只有当对象被创建后 this 才有意义,因此不能在静态成员函数中使用。

空指针访问成员函数

c++中空指针也是可以调用this指针的,但是也要注意有没有用到this指针

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

#include<iostream>
using namespace std;

class Person {
public:
	int m_Age;
	void showClass() {
		cout << "这是小学一年级" << endl;
	}
	void showAge() {
		//对this做判空处理,增强代码的健壮性,防止空指针带来的异常
		if (this == NULL) {
			cout << "空指针"<< endl;
			return;
		}
		cout << "你的年龄" << this->m_Age << endl;
	}
};
void test01() {
	//创建一个空指针
	Person* p = NULL;
	p->showClass();//空指针可以调用成员函数
	p->showAge();//由于空指针直接调用导致程序崩溃 解决方案,在成员函数中,对this多加一层判断
}
int main() {
	test01();
	system("pause");
	return 0;
}

const修饰成员函数

常函数:

  1. 成员函数后加上const后我们称这个函数为常函数
  2. 常函数内不可以修改成员属性
  3. 只有在成员属性声明时加上关键字mutable,才可以在常函数内进行修改

常对象:

  1. 声明对象前加const,称声明对象为常对象
  2. 常对象只能调用常函数
#include<iostream>
using namespace std;
class P {
public:
	int m_A;
	mutable int m_B; // mutable 修饰的成员属性

	//常函数
	void changeValue() const {
		//this->m_A = 200;  常函数内不可以修改成员属性
		m_B = 20;// mutable 修饰的成员属性可以在常函数进行修改
	}

	void func() {
	}
};

void test01() {
	P p;
	p.changeValue();
}
void test02() {
	//在对象前加上关键字const修饰,变为常对象
	const P p;
	//p.m_A = 20;
	p.m_B = 30;//被mutable修饰的成员属性,在常对象下也可以进行修改
	//p.func(); //常对象不可以调用普通的成员函数,因为普通的成员函数可以修改成员属性
	p.changeValue();//常对象只能调用常函数
}
int main() {

	test01();
	test02();
	system("pause");
	return 0;
}

4、友元

在 C++中,一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。

但是借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。

友元函数:在函数前面加 friend 关键字,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。

友元的三种实现:

1、全局函数声明为友元函数

#include <iostream>
using namespace std;

class Building {
	//将全局函数goodFriend()声明为友元函数,就可以访问私有成员
	friend void goodFriend(Building* b);
public:
	Building() {
		m_LivingRoom = "客厅";
		m_BedRoom = "卧室";
	}
public:
	string m_LivingRoom;
private:
	string m_BedRoom;
};
//全局函数
void goodFriend(Building* b) {
	cout << "好朋友正在访问" << b->m_LivingRoom << endl;
	cout << "好朋友正在访问" << b->m_BedRoom << endl;  //访问私有成员
}

void test01() {
	Building b;
	goodFriend(&b);
}
int main() {

	test01();
	system("pause");
	return 0;
}

2、类做友元

不仅可以将一个函数声明为一个类的“朋友”,还可以将整个类声明为另一个类的“朋友”,这就是友元类。友元类中的所有成员函数都是另外一个类的友元函数。

例如将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性的。

#include <iostream>
using namespace std;
//类做友元

class Building {
	//GoodFriend声明为Building的友元类,可以访问Building的所有成员
	friend class GoodFriend;
public:
	Building();//声明构造函数,类外实现

public:
	string m_LivingRoom;
private:
	string m_BedRoom;
};

class GoodFriend {
public:
	GoodFriend();
	void visit(); //用来访问Building的成员
	Building * b;
};

//类外实现构造函数
Building::Building() {
	m_LivingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodFriend::GoodFriend() {
	b = new Building;
}
void GoodFriend::visit() {
	cout << "好朋友正在访问" << b->m_LivingRoom << endl;
	cout << "好朋友正在访问" << b->m_BedRoom << endl;
}

int main() {

	GoodFriend g;
	g.visit();
	system("pause");
	return 0;
}

3、将其他类的成员函数声明为友元函数

#include <iostream>
using namespace std;

class Building; //提前声明Building类

//声明GoodFriend类
class GoodFriend {
public:
	GoodFriend();
	void visit(); //参观函数,用来访问Building的成员
	void visit2(); //参观函数2,不能访问Building的私有成员
private:
	Building* b;
};
//声明Building类
class Building {
	//将GoodFriend类中的成员函数visit()声明为友元函数
	friend void GoodFriend::visit();
public:
	Building();

public:
	string m_LivingRoom;
private:
	string m_BedRoom;
};
//类外实现成员函数
Building::Building() {
	m_LivingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodFriend::GoodFriend() {
	b = new Building;
}
void GoodFriend::visit() {
	cout << "1好朋友正在访问" << b->m_LivingRoom << endl;
	cout << "1好朋友正在访问" << b->m_BedRoom << endl;
}
void GoodFriend::visit2() {
	cout << "2好朋友正在访问" << b->m_LivingRoom << endl;
	/*cout << "好朋友正在访问" << b->m_BedRoom << endl;*/
}
void test01() {
	GoodFriend g;
	g.visit();
	g.visit2();

}
int main() {

	test01();
	system("pause");
	return 0;
}

注意事项:

  • 将 GoodFriend类声明在Building类前面,这是因为编译器从上到下编译代码,visit() 函数体中用到了 Building类的成员 m_LivingRoom和m_BedRoom,如果提前不知道 Building的具体声明内容,就不能确定 Building类是否拥有该成员(类的声明中指明了类有哪些成员)。
  • 一个函数可以被多个类声明为友元函数,这样就可以访问多个类中的 private 成员。

5、运算符重载

运算符重载(Operator Overloading):同一个运算符可以有不同的功能。

通过运算符重载,扩大了C++已有运算符的功能,使之能用于对象。

加号运算符重载

把两个对象的成员分别相加,返回最终的对象

#include<iostream>
using namespace std;

//加号运算符重载

//1.成员函数做加号运算符重载
class P {
public:
	int m_A;
	int m_B;
	P() {};
	P(int a, int b);
    //重载运算符有一个返回类型和一个参数列表
	//P operator+(P& p) 对象作为参数进行传递
	//{
	//	P temp;
	//	temp.m_A = this->m_A + p.m_A;  对象的属性使用 this 运算符进行访问
	//	temp.m_B = this->m_B + p.m_B;
	//	return temp;
	//}
};

P::P(int a, int b) 
{
	this->m_A = a;
	this->m_B = b;
}

//2.全局函数做加号运算符重载
P operator+(P& p1, P& p2) 
{
	P temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
void test01() 
{
	P p1(4, 8);
	P p2(6, 2);
	P p3 = p1 + p2; //可以直接用operator+,也可以用+号
	cout << p3.m_A << endl;
	cout << p3.m_B << endl;
}

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

左移运算符重载

cout 是 ostream 类的对象,cin 是 istream 类的对象

#include<iostream>
using namespace std;

//左移运算符重载
class P {
    //了能够直接访问private 成员变量,同样需要将该函数声明为友元函数:
	friend ostream& operator<< (ostream& cout, P& p);
private:
	int m_A;
	int m_B;
public:
	P(int a, int b) {
		m_A = a;
		m_B = b;
	}
};

//只能用全局函数来重载左移运算符
//由于cout全局只能有一个,所以要采用引用的方式去拿到
ostream& operator<< (ostream& cout, P& p) {

	cout << "p.m_A的值为:" << p.m_A << "  p.m_B的值为:" << p.m_B;
	return cout;//返回了对象的引用,所以重载后的运算符可以实现连续输出

}
int main() {
	P p(10, 20);
	cout << p << " 你好呀" << endl;
	system("pause");
	return 0;
}

重载++和–运算符

自增++和自减--都是一元运算符,它的前置形式和后置形式都可以被重载。

#include<iostream>
using namespace std;

class MyInterger {
	friend ostream& operator<<(ostream& cout, MyInterger myInt);
public:
	MyInterger() {
		m_Int = 0;
	}

	//重载前置++运算符
	MyInterger& operator++() {
		m_Int++;
		return *this;
	}
	//重载后置++运算符
	//int表示占位参数,可以用来区分前置和后置运算符
	MyInterger operator++(int) {
		MyInterger temp = *this;//先记录当前结果,再进行++操作
		m_Int++;
		return temp;
	}
	//重载前置--运算符
	MyInterger& operator--() {
		m_Int--;
		return *this;
	}
	//重载后置--运算符
	//int表示占位参数,可以用来区分前置和后置运算符
	MyInterger operator--(int) {
		MyInterger temp = *this;//先记录当前结果,再进行--操作
		m_Int--;
		return temp;
	}
private:
	int m_Int;
};

//重载左移运算符
ostream& operator<<(ostream& cout, MyInterger myInt) {
	cout << myInt.m_Int;
	return cout;
}
void test01() {
	MyInterger myInt;
	cout << ++myInt << endl;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

void test02() {
	MyInterger myInt;
	cout << myInt++ << endl;
	cout << myInt++ << endl;
	cout << myInt << endl;
}
void test03() {
	MyInterger myInt;
	cout << --myInt << endl;
	cout << --(--myInt) << endl;
	cout << myInt << endl;
}

void test04() {
	MyInterger myInt;
	cout << myInt-- << endl;
	cout << (myInt--)-- << endl;
	cout << myInt << endl;
}
int main() {
	cout << "前置++" << endl;
	test01();
	cout << "后置++" << endl;
	test02();
	cout << "前置--" << endl;
	test03();
	cout << "后置--" << endl;
	test04();
	system("pause");
	return 0;
}

赋值运算符重载

#include<iostream>
using namespace std;
//赋值运算符重载

class Person {
public:
	int* m_Age;

	Person(int age) {
		m_Age = new int(age);
	}

	~Person() {
		if (m_Age == NULL) {
			return;
		}
		else {
			delete m_Age;
			m_Age = NULL;
		}
	}

	Person& operator=(Person& p) {
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
		//深拷贝
		m_Age = new int(*p.m_Age);
		return *this;
	}
};

void test01() {
	Person p(18);
	Person p3(20);
	Person p2(30);
	p3 = p = p2;
	cout << "p的年龄为:" << *p.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

关系运算符重载

#include <iostream>
using namespace std;

class Person {
public:
	Person(string name, int age) {
		m_Name = name;
		m_Age = age;
	}
	string m_Name;
	int m_Age;

	bool operator==(Person& p) {
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
			return true;
		}
		else {
			return false;
		}
	}
	bool operator!=(Person& p) {
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
			return false;
		}
		else {
			return true;
		}
	}
};

void test01() {
	Person p1("张三", 18);
	Person p2("张三", 34);

	if (p1 != p2) {
		cout << "p1和p2是不相等的" << endl;
	}
	else {
		cout << "p1和p2是相等的" << endl;
	}
}
int main() {
	test01();
	system("pause");
	return 0;
}

函数调用运算符重载

  1. 函数调用运算符()也可以进行重载
  2. 由于重载后的使用方式非常像函数的调用,因此称为仿函数
  3. 仿函数没有固定写法,非常灵活

6、继承

一个类从另一个类获取成员变量和成员函数的过程

基本语法

class 子类 : 继承方式 父类

子类也被称为 派生类,父类也被称为 基类

class Student: public People

三种继承方式

公共继承 public(常用)

  • 父类中的公共权限到子类中依旧是公共权限

  • 父类中的保护权限到子类中依旧是保护权限

  • 子类无法访问父类中的私有权限

保护继承 protected

  • 父类中的公共权限和保护权限在子类中变成了保护权限

  • 子类无法访问父类中的私有权限

私有继承 private(默认)

  • 父类中的公共权限和保护权限到子类中变成了私有权限

  • 子类无法访问父类中的私有权限

不同继承方式对不同属性的成员的影响结果:

在这里插入图片描述

protected 成员和 private 成员类似,都不能通过对象访问。但是当存在继承关系时,protected 和 private 就不一样了;父类中的 protected 成员可以在子类中使用,而父类中的 private 成员不能在子类中使用。

改变访问权限

使用 using 关键字可以改变父类成员在子类中的访问权限,例如将 public 改为 private、将 protected 改为 public。

注意:using 只能改变父类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为父类中 private 成员在子类中是不可见的,根本不能使用,所以父类中的 private 成员在子类中无论如何都不能访问。

#include<iostream>
using namespace std;

//父类Teacher
class Teacher {
public:
    void show();
protected:
    char m_name;
    int m_age;
};
void Teacher::show() {
    cout << m_name << "的年龄是" << m_age << endl;
}

//子类Student
class Student : public Teacher {
public:
    void learning();
public:
    using Teacher::m_name;  //将protected改为public
    using Teacher::show;  //将public改为protected
    float m_score;
private:
    using Teacher::m_age;  //将protected改为private
};
void Student::learning() {
    cout << "我是" << m_name << ",今年" << m_age << "岁,这次考了" << m_score << "分!" << endl;
}

int main() {
    Student stu;
    stu.m_name = '小明';
    stu.m_age = 16;//编译错误
    stu.m_score = 99.5f;
    stu.show();  
    stu.learning();
    return 0;
}

继承时同名成员处理方式

遮蔽问题:如果子类中的成员(包括成员变量和成员函数)和父类中的成员重名,那么就会遮蔽从父类继承过来的成员,不管它们的参数是否一样。在子类中使用该成员(包括在定义子类时使用,也包括通过子类对象访问该成员)时,实际上使用的是子类新增的成员,而不是从父类继承来的;而且父类成员函数和子类成员函数不会构成重载。

访问静态和非静态同名成员规则:

  • 访问子类同名成员,直接访问即可

  • 访问父类同名成员时,需要加上作用域

# include<iostream>
using namespace std;

//继承同名成员处理方式
class Base {
public:
	Base() {
		m_A = 100;
	}
	int m_A;
	void say() {
		cout << "我是父类的say函数" << endl;
	}
};

class Son :public Base {
public:
	Son() {
		m_A = 200;
	}
	int m_A;
	void say() {
		cout << "我是子类的say函数" << endl;
	}
};

void test01() {
	Son s;
	cout << "子类的m_A变量= " << s.m_A << endl;
	cout << "父类下面的m_A变量= " << s.Base::m_A << endl;
}

void test02() {
	Son s;
	s.say();
	s.Base::say();
}
int main() {
	cout << "同名的成员属性处理方式" << endl;
	test01();
	cout << "同名的成员函数处理方式" << endl;
	test02();
	system("pause");
	return 0;
}

父类和子类的构造函数

类的构造函数不能被继承。因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。但是子类的构造对象可以调用父类的构造函数。

构造函数调用顺序:先调用父类,再调用子类,只能调用直接父类的构造函数,不能调用间接父类的

析构函数调用顺序:先调用子类,再调用父类

多继承

C++支持多继承(Multiple Inheritance),即一个子类可以有两个或多个父类

语法:

class 子类:继承方式 父类1,继承方式 父类2...

多继承可能导致父类中同名成员的出现,需要加作用域加以区分

c++实际开发中,不建议用多继承

菱形继承

两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承或者钻石继承:

菱形继承

如上:类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A–>B–>D 这条路径,另一份来自 A–>C–>D 这条路径。

在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B–>D 这条路径,还是来自 A–>C–>D 这条路径。

//间接基类A
class A {
protected:
    int m_a;
};

//直接基类B
class B : public A {
protected:
    int m_b;
};

//直接基类C
class C : public A {
protected:
    int m_c;
};

//派生类D
class D : public B, public C {
public:
    void seta(int a) { m_a = a; }  //命名冲突,为了消除歧义,可以在 m_a 的前面指明它具体来自哪个类
    void setb(int b) { m_b = b; }  //正确
    void setc(int c) { m_c = c; }  //正确
    void setd(int d) { m_d = d; }  //正确
private:
    int m_d;
};

int main() {
    D d;
    return 0;
}

虚继承

虚继承解决了多继承时的命名冲突和冗余数据问题,使得在派生类中只保留一份间接基类的成员:

菱形继承和虚继承

在继承方式前面加上 virtual 关键字就是虚继承:

//间接基类A
class A {
protected:
    int m_a;
};

//直接基类B
class B : virtual public A {  //虚继承
protected:
    int m_b;
};

//直接基类C
class C : virtual public A {  //虚继承
protected:
    int m_c;
};

//派生类D
class D : public B, public C {
public:
    void seta(int a) { m_a = a; }  //正确
    void setb(int b) { m_b = b; }  //正确
    void setc(int c) { m_c = c; }  //正确
    void setd(int d) { m_d = d; }  //正确
private:
    int m_d;
};

int main() {
    D d;
    return 0;
}

这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

7、多态

“多态(polymorphism)”指的是同一名字的事物可以完成不同的功能。

多态可以分为编译时的多态和运行时的多态。

编译时的多态(静态多态):主要是指函数的重载(包括运算符的重载)、对重载函数的调用,在编译时就能根据实参确定函数地址,然后确定调用哪个函数。

运行时的多态(动态多态):和继承、虚函数等概念有关,运行阶段确定函数地址。

构成动态多态的满足条件:

  • 有继承关系
  • 子类重写父类的虚函数
  • 存在父类的指针,通过该指针调用虚函数。

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

虚函数

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

虚函数是根据指针的指向来调用的,指针指向哪个类的对象就调用哪个类的虚函数。

纯虚函数

将虚函数声明为纯虚函数,语法格式为:

virtual 返回值类型 函数名 (函数参数) = 0;

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。

抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。

只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。

通常是基类作为抽象类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化

虚析构和纯虚析构

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

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

虚析构和纯虚析构异同:

同:

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

异:

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

语法:

//虚析构
virtual ~类名(){}
//纯虚析构
virtual ~类名(){} = 0;

5、文件操作

操作文件的三大类

C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:

  • ifstream:专用于从文件中读取数据;
  • ofstream:专用于向文件中写入数据;
  • fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。

这 3 个文件流类都位于 <fstream> 头文件中,因此在使用它们之前,程序中应先引入此头文件。

这 3 个文件流类的继承关系:

img

fstream类常用成员方法:

在这里插入图片描述

文件类型

文本文件:文件以文本的ASCII码形式存储在计算机

二进制文件:文件以文本的二进制形式存储在计算机中

打开文件

在对文件进行读写操作之前,先要打开文件。可以通过以下两种方式打开文件:

  • 调用流对象的 open 成员函数打开文件。
  • 定义文件流对象时,通过构造函数打开文件。

使用open函数打开文件:

void open(const char* szFileName, int mode)
  • 第一个参数是指向文件名的指针,

  • 第二个参数是文件的打开模式标记,文件的打开模式标记代表了文件的使用方式,这些标记可以单独使用,也可以利用|操作符组合使用。

下标列出各种模式标记单独使用时的作用,以及常见的两种模式标记组合的作用:

模式标记适用对象作用
ios::inifstream fstream打开文件用于读取数据。如果文件不存在,则打开出错。
ios::outofstream fstream打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。
ios::appofstream fstream打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。
ios::ateifstream打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。
ios:: truncofstream打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。
ios::binaryifstream ofstream fstream以二进制方式打开文件。若不指定此模式,则以文本模式打开。
ios::in | ios::outfstream打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::outofstream打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。
ios::in | ios::out | ios::truncfstream打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。

1、文本文件

写文件

//包含头文件
#include<fstream>

using namespace std;

#include<iostream>

void test()
{
    //创建流对象
	ofstream ofs;
//打开文件
	ofs.open("test.txt", ios::out);
//写入文件内容
	ofs << "姓名:张三" << endl;
	ofs << "年龄:118" << endl;
	ofs << "性别:男" << endl;
//关闭流
	ofs.close();
}

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

读文件

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

void test()
{
	ifstream ifs;

	ifs.open("test.txt", ios::in);
	if (!ifs)
	{
		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){
		cout << c;
	}
	
	ifs.close();
}

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

2、二进制文件

写文件

将内存中 buffer 指向的 count 个字节的内容写入文件

ostream & write(char* buffer, int count);
  • buffer 用于指定要写入文件的二进制数据的起始位置;

  • count 用于指定写入字节的个数。

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

class Person
{
public:
	char m_name[64];
	int m_age;
};

void test()
{
	ofstream ofs;

    //以二进制文本模式打开文件
	ofs.open("person.txt", ios::out | ios::binary);
	Person p = {"张三",18};
   
	ofs.write((const char*)&p, sizeof(p));

	ofs.close();
}

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

读文件

从文件中读取 count 个字节的数据

istream & read(char* buffer, int count);
  • buffer 用于指定读取字节的起始位置,
  • count 指定读取字节的个数
#include<fstream>
using namespace std;
#include<iostream>

class Person
{
public:
	char m_name[64];
	int m_age;
};

void test()
{
	ifstream ifs;

	ifs.open("person.txt", ios::out | ios::binary);

	if (!ifs)
	{
		cout << "文件读取失败" <<endl ;
		return;
	}
	Person p;
	ifs.read((char*)&p, sizeof(p));
	cout << "p的姓名" << p.m_name << "p的年龄:" << p.m_age << endl;
	ifs.close();
}

int main()
{
	test();
	system("pause");
	return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++核心编程 的相关文章

  • Linux第二课:Ubuntu 操作入门(内含:1Ubuntu 下打开终端+2 Linux 文件属性+3 设置屏幕+4 系统关机与重启+5.文件浏览器)

    Ubuntu 操作入门 2 2 1Ubuntu 下打开终端 方法1 点击 Ubuntu 桌面左上角图标进入搜索框 xff0c 输入 term 可以弹出终端 Terminal 程序 方法2 xff1a 桌面或者在文件浏览器的任何目录下右键鼠标
  • 堆中存什么?栈中存什么?

    堆中存的是对象 栈中存的是基本数据类型 和堆中对象的引用 一个对象的大小是不可估计的 xff0c 或者说是可以动态变化的 xff0c 但是在栈中 xff0c 一个对象只对应了一个4btye的引用 xff08 堆栈分离的好处 xff1a xf
  • 计算一个数的N次方

    计算一个数的N次方时 xff0c 我们先设定两个参数n和k xff0c n表示你要输入的数 xff0c k表示这个数的次方 这个时候我们必须对次方数k作出分类 xff1a k 61 0 return 1 其他 xff1a return n
  • 用结构体编写电话通讯录

    用结构体数组编写电话通讯录 xff0c 必须得知道结构体的形式 xff0c 那先把结构体定义回顾一下 xff1a 一般形式为 xff1a xff08 1 xff09 struct 结构体名称 成员表列 数组名 数组长度 如 xff1a st
  • linux(centos)下安装git并上传代码些许步骤(亲自验证过的步骤)

    以前听说了好多次github xff0c 但直到最近才第一次学习使用github来托管自己在linux下的代码 xff01 说实话 xff0c 我自己在使用的时候从网上查了好多教程 xff0c 但总觉得难以掌握 xff08 步骤过于繁琐 x
  • shell具体执行过程及自主实现shell解释器

    在编写shell 解释器之前 xff0c 先来分析几个知识点 xff1a xff08 1 xff09 shell 执行命令时步骤 xff1a xff08 如下图 xff09 xff08 2 xff09 shell 执行脚本时的步骤 xff1
  • Linux下的桥接模式和Nat模式的区别

    先来看一下linux在的桥接模式和Nat模式的差别 xff1a 桥接模式 xff1a Nat模式 xff1a 真正的接触这个问题是因为同学要给我远程传输文件 xff0c 这个时候就调节至桥接模式下 xff0c 进行ping 尽管我们用的是同
  • C知识点整合

    C语言总结 一 语法 1 常见的数据内置类型所占字节 xff08 64 位下 xff09 xff1a char 1 int 4 float 4 long 4 double 8 Longlong 8 2 变量 xff1a xff08 1 xf
  • 判断一棵二叉树是否为完全二叉树

    1 完全二叉树的特点 xff08 来自专业定义 xff09 看到上面完全二叉树的特点 xff0c 我可以将其特点按照自己的 理解归纳为以下几点 xff1a xff08 1 xff1a 若二叉树最下面一层有节点出现 xff0c 那么这个节点一
  • 深入理解JNI技术

    一 JNI是什么 xff1f JNI是Java Native Interface的缩写 xff0c 译为Java本地调用 JNI是一种技术 二 JNI技术的用途 xff1f Java程序中的函数调用Native程序中的函数 Native一般
  • HTTP基本认证(Basic Authentication)

    在浏览网页时候 xff0c 浏览器会弹出一个登录验证的对话框 xff0c 如下图 xff0c 这就是使用HTTP基本认证 1 客户端发送http request 给服务器 服务器验证该用户是否已经登录验证过了 xff0c 如果没有的话 xf
  • 将字符串以单词为单位逆序"I am a Student" 解法

    网上有个题目 xff0c 将字符串以单词为单位逆序 例如 34 I am a Student 34 要变成 34 Student a am I 34 解法大致为 xff1a 先将字符串整体逆序第一个字符和最后一个交换 xff0c 第二个与倒
  • 堆排序查找前N个最大数和二分查找算法

    先了解堆排序概念 堆排序利用了大根堆 xff08 或小根堆 xff09 堆顶记录的关键字最大 xff08 或最小 xff09 这一特征 xff0c 使得在当前无序区中选取最大 xff08 或最小 xff09 关键字的记录变得简单 xff08
  • 构建hash表和两种处理冲突方法

    hash表定义 hashing定义了一种将字符组成的字符串转换为固定长度 xff08 一般是更短长度 xff09 的数值或索引值的方法 xff0c 称为散列法 xff0c 也叫哈希法 由于通过更短的哈希值比用原始值进行数据库搜索更快 xff
  • 用hash_map统计出现次数最多的前N个URL

    海量数据统计频率最高词汇的常规办法之一是先通过一个hash函数处理数据然后取模N xff0c 拆分为N个小文件 xff0c 对每一个小文件进行词频统计和排序处理 xff0c 然后归并N个小文件取频率最大的M个数 下面程序是利用hash ma
  • C++与java语法的异同整理

    文章目录 Java与C 43 43 与基础语法异同java的认知 amp java与C 43 43 的异同C 43 43 中的虚函数和JAVA中的抽象方法区别C 43 43 虚函数与JAVA中抽象函数比较关于接口与抽象类 xff1a Jav

随机推荐

  • 微信公众平台-股票行情查询

    微信公众平台 股票行情查询 php实现的获取上证 xff0c 深证 A B股实时行情的接口 xff0c 只实现了文本消息回复 xff0c K线图可以在图文消息中加上接口url地址就可以显示 xff0c 具体的接口地址网上可以找 xff0c
  • PELCO-D与PELCO-P协议介绍

    一般控制协议都由硬件或软件商编制在程序里面 xff0c 我们只需要通过相关的控制设备来进行操作 但是作为一个从事监控行业的技术人员 xff0c 往往会遇到除了电脑和协议转换器以外根本没有任何控制设备的情况 xff0c 此时 xff0c 协议
  • ROS 问题(topic types do not match、topic datatype/md5sum not match、msg xxx have changed. rerun cmake)

    1 topic types 不匹配 使用 roslaunch 命令 roslaunch carla ros bridge carla ros bridge with example ego vehicle launch 启动官方 demo
  • Ubuntu中查看安装的Python版本以及不同版本之间切换

    查看系统中已安装的所有Python版本 使用 ls 命令来查看你的系统中都有那些 Python 的二进制文件可供使用 xiyou 64 span class token property xiyou virtual machine span
  • Ubuntu Python 多版本安装

    概述 由于 Python 3 有几次较为跳跃的更新 xff0c 导致大量使用 Python 3 作为开发工具的软件会对 Python 3 的版本进行严格限制 xff0c 如限制使用 Python 3 8 Python 3 9 版本 这要求开
  • 怒爬某 Hub 资源就为撸了一个鉴黄平台

    来源 xff1a 码匠笔记公号 黄色已经是我们所不容然而却防不胜防的 xff0c 尤其是对于做内容的工具和平台 xff0c 所以花了30分钟搭建了一个鉴黄平台 xff0c 分享给大家 数据准备 找了 N 多资源都不能解决问题 xff0c 于
  • ROSERROR : datatype/md5sum

    出错原因是 xff1a 自定义消息 xff0c 发送话题的消息类型和接受话题的消息类型不一样 但是我的代码真的是一样的 所以 xff0c 解决办法是 xff1a 清空工作空间的build devel文件夹 xff0c 重新编译运行 成功 x
  • 学习使用 ArUco 标记

    在本文中 xff0c 我们将研究使用 Python 和 OpenCV 检测和估计 ArUco 标记的方向 首先 xff0c 我们将从生成 ArUco 标记开始 你可以使用特定网站一个一个地创建单个标签 xff0c 也可以使用我的程序生成整个
  • TCP协议中的序列号

    TCP 协议工作在OSI的传输层 xff0c 是一种可靠的面向连接的数据流协议 xff0c TCP之所以可靠 xff0c 是因为它保证了传送数据包的顺序 顺序是用一个序列号来保证的 响应包内也包括一个序列号 xff0c 表示接收方准备好这个
  • c++开发过程中遇到的问题及解决方案

    xfeff xfeff 问题一 xff1a 1 gt JForm obj error LNK2019 无法解析的外部符号 34 public virtual thiscall JFC JForm JForm void 34 1JForm 6
  • Digest Auth 摘要认证

    Digest Auth 摘要认证 1 非常规方式 转载 xff1a https blog csdn net qq 25391785 article details 86595529 public static void postMethod
  • 嵌入式C语言基础知识--位操作

    目录 大小端模式 xff1a 字节高低位 xff1a LSB和MSB xff1a 高位先行msb 低位先行lsb xff1a 串口传输是低位先行 IIC传输是高位先行 字节序 比特序 大端模式 小端模式 高字节序 低字节序 MSB LSB
  • qt 绘制 流程图 案例 收集

    参考 C 43 43 中的例子 xff1a Qt Qt5 11 1 Examples Qt 5 11 1 widgets graphicsview diagramscene The Diagram Scene example is an a
  • 获得GPS数据的两种方法 1.读串口

    获得GPS数据一般可通过两种方法 xff0c 读串口及调用gpsapi函数 串口作为硬件设备 xff0c 不能同时被两个程序占用 xff0c gpsapi函数几个应用程序可同时共享端口 1 xff0e 读串口 先找出 gps 使用的串口号
  • 机器人履带底盘的悬挂和传动

    我爸是坦克 xff01 一个履带机器人 一 前言 曾经调研过机器人履带底盘的设计和构造 xff0c 整理一下备忘 本文主要关注自己比较难理解的履带底盘悬挂机构和摇臂式履带底盘传动机构 其中履带底盘的悬挂涉及到克里斯蒂悬挂 xff08 Chr
  • Python爬虫——Scrapy 的基本使用

    文章目录 Python爬虫 Scrapy 的基本使用1 创建 Scrapy 爬虫项目2 Scrapy 创建爬虫文件3 Scrapy 运行爬虫文件 Python爬虫 Scrapy 的基本使用 Scrapy 框架中创建项目 查看配置信息 xff
  • Python爬虫——Scrapy框架使用实例及执行过程

    文章目录 Python爬虫 Scrapy框架使用实例及执行过程1 Scrapy框架使用实例2 Scrapy框架执行过程 Python爬虫 Scrapy框架使用实例及执行过程 1 Scrapy框架使用实例 1 创建scrapy项目 scrap
  • C++基础入门

    文章目录 C 43 43 基础入门1 变量和常量2 关键字3 数据类型4 流程控制5 数组6 函数7 指针8 结构体 C 43 43 基础入门 1 变量和常量 变量和常量 变量 xff1a 数据类型 变量名 61 值 常量 xff1a 宏常
  • 解决CLion的 CMake executable not found:XXX

    解决CLion的 CMake executable not found XXX 问题 CLion不能编译DeepStream项目 xff0c 也不能 build 和 debug了 xff0c 还出现以下提示 xff1a 原因 原因是 cli
  • C++核心编程

    C 43 43 核心编程 1 c 43 43 内存模型 c 43 43 程序执行时将内存大致分为4个区域 代码区 xff1a 存放CPU执行的二进制代码指令 xff0c 由操作系统进行管理全局区 xff1a 存放全局变量和静态变量以及全局常