C++基础--面向对象一

本文详细介绍了C++中new关键字的内存管理,引用的概念及其在函数参数传递中的应用,函数重载规则,类与对象的封装、构造析构、初始化列表、静态成员以及对象特性如this指针、空指针和运算符重载。通过实例演示,深入理解这些核心概念。
摘要由CSDN通过智能技术生成

主要参考:黑马程序员C++课程

1. new

C++利用new在堆区开辟数据,由delete手动释放。
语法:new 数据类型
返回:该数据对应类型的指针

int* func() {
	int* p = new int[10];
	for (int i = 0; i < 10; i++) {
		p[i] = i + 1;
	}
	return p;
}

int main() 
{
	int* p = func();
	for (int i = 0; i < 10; i++) {
		cout << p[i] << endl;
	}
	delete[] p;	//p变为野指针
	//delete释放一个变量,delete[]释放数组
	return 0;
}

2. 引用

给变量起别名。引用的本质也是指针。
语法:数据类型 &别名=原名

int a = 10;
int& b = a;  //引用必须初始化,之后不可改变即b再作为c的别名
//引用后,对ab的操作等同

//区别于值传递和地址传递,作为函数参数可以使用引用传递
void swap(int &a, int &b) { //这里的&a其实就是a的别名
	int temp = a;
	a = b;
	b = temp;
	return ;
}
int main() {
	int a = 10, b = 20;
	swap(a, b);
	cout << a << b << endl;
	return 0;
}

//引用作为函数的返回值
int& func() {
	static int a = 10; 
	//这里加static原因是引用返回值的函数不能返回局部变量
	//加了static的变量存放在全局区,程序结束才释放。
	return a;
}
int main() {
	int& b = func();
	cout << b << endl;
	func() = 20; //引用为返回值的函数可以做左值
	cout << func() << endl;
	return 0;
}

3. 函数重载

必须在一个作用域下。

void func(int a, char b) {cout << "111" << endl;}
void func(char a, int b) {cout << "444" << endl;}
void func(char a) {cout << "222" << endl;}
int func(char a) {cout << "333" << endl;return 0;}
int main() {
	func(1, '1');
	func('2');     //参数个数和类型不同的重载
	func('2');     //无法重载仅按返回值类型区分的函数(避免二义性)
	func('1', 1);  //参数顺序不同的重载
	return 0;
}
//注意事项
//函数默认参数使用需要注意重载。

4. 类和对象

类与结构体区别:不写权限的情况下,class默认private,struct默认public。
PS
类的成员变量和成员函数是分开存储的,静态成员变量和静态/非静态成员函数都不属于类的对象上。
空对象占用1字节,是编译器为了区分空对象占内存的位置分配的。

4.1 封装

意义:将属性和方法统一为整体(成员),并加以权限控制。
访问权限

class abc {
public:
	//公共权限,成员类内外都可以访问
protected:
	//保护权限,成员只有类内可以访问,允许子类继承
private:
	//私有权限,成员只有类内可以访问,不允许子类继承
};

分包:对于大的工程来说,所有类写在一个作用域下不现实,一般拆成.h和.c两个文件。前者保存类所需的头文件,类的成员属性和方法的声明,后者保存方法的具体实现。

4.2 成员私有化

优点:
1.自己控制读写的权限。
2.对于写可以进行数据有效性检测。

class Person {
public:
	void setname(string a) { 
		if (a.size() != 4) { cout << "re-input" << endl; return; }
		name = a;
	}
	string getname(){ return name; }
	int getage() { age = 12; return age; }

private:
	string name; //可读可写--要求名字只能两个字
	int age;     //只读
};

int main() {
	Person p;
	p.setname("张三");
	cout << p.getname() << endl;
	cout << p.getage() << endl;
	return 0;
}

4.3 对象特性–构造和析构

4.3.1 构造和析构:
构造函数:用于创建对象时为对象的成员属性赋值,权限为pubilc。
语法:类名(可以写参数,因此可以重载){}
析构函数:用于对象销毁前的清理工作,权限为public。
两者都由编译器自动调用。
语法:~类名(不可以有参数){}
顺序:析构先进后出,构造相反

4.3.2 构造函数分类及调用:
按参数分:有参/无参
按类型分:普通构造/拷贝构造
三种调用方式:括号法,显示法,隐式转换法。

class Person {
public:
	//普通构造
	Person() { age = 10; }     //无参/默认
	Person(int a) { age = a; } //有参
	//拷贝构造--用已有成员初始化新成员
	Person(const Person& p) { age = p.age; }

	~Person() {}

	int age;
};

void test() {
	//1.括号法
	Person p; //默认构造不要加(),否则编译器会认为是函数声明
	Person p1(12);
	Person p2(p);
	cout << p.age << p1.age << p2.age << endl;

	//2.显示法
	Person q;
	Person q1 = Person(12); //=右侧是匿名对象
	Person q2 = Person(q);  //匿名对象在当前行调用结束后系统会立刻析构
	//Person (q2);-----这是错误的,用拷贝构造初始化匿名对象,编译器会忽视(),将其当作默认构造,导致对象重定义
	cout << q.age << q1.age << q2.age << endl;


	//3.隐式转换法
	Person a = 10; //相当于Person a = Person(10);
	Person b = a;  //相当于Person b = Person(a);
}

4.3.3 拷贝构造使用时机:

void work(Person p) {}

Person work1() {
	Person p(10);
	return p;  //这里拷贝构造了1个p作为返回值
}

void test() {
	//1.用旧的对象初始化新的对象
	Person p1(12);
	Person p2(p1);

	//2.值传递方式给函数参数传值
	work(p1); //这里其实拷贝构造了一个临时p1传给work()

	//3.值方式返回局部对象
	Person p3 = work1();

	cout << p1.age << p2.age << p3.age << endl;
}

4.3.4 深拷贝与浅拷贝:
浅拷贝:简单的赋值拷贝(可能导致堆区内存重复释放)
深拷贝:在堆区重新申请内存,进行拷贝操作

class Person {
public:
	Person(int a, int b) { 
		age = a; 
		height = new int(b); //在堆区申请内存
	}
	//如果不自定义拷贝构造函数,编译器自动调用默认拷贝,只能进行浅拷贝
	//浅拷贝导致p1和p2的height指向同一块内存。
	//导致析构时p2先释放了height指向的内存,p1析构时就产生重复释放
	//最终导致程序崩溃
	Person(const Person& p) { 
		age = p.age; 
		height = new int(* p.height);  //在堆区重新申请内存进行深拷贝
	}

	~Person() { 
		if (height != NULL) {
			delete(height);
			height = NULL;
		}
	}

	int age;
	int* height;
};

int main() 
{
	Person p1(23, 170);
	Person p2(p1);
	cout << *p1.height << *p2.height << endl;
	return 0;
}

4.4 对象特性–初始化列表

用于初始化类属性。
语法:构造函数():属性(值)…{}

class Person {
public:
	Person(int a, int b, int c):A(a), B(b), C(c){}

	int A;
	int B;
	int C;
};

int main() {
	Person p(11, 22, 33); //已经由初始化列表赋初值
	cout << p.A << p.B << p.C << endl;
	return 0;
}

main中调用看起来和有参构造一样,感觉没什么用,或许意义在于代码更简洁?

4.5 对象特性–静态成员

静态成员变量
1.所有对象共享一份数据,也就是说不属于某一个特定的对象。
2.在编译阶段就分配全局区内存。
3.必须类内声明,类外初始化。

class Person {
public:
	static int A;
};

int Person::A = 100;  

int main() {
	Person p1;
	cout << p1.A << endl;  //通过对象访问
	Person p2;
	p2.A = 200;  //由于数据共享,这里p1.A也变为200了
	cout << p1.A << endl;
	cout << Person::A << endl; //通过类名访问
	return 0;
}

静态成员方法
1.所有对象共享一个静态成员函数。
2.只能访问静态成员变量(因为无法区分非静态成员变量属于哪一个对象)。

class Person {
public:
	static void func() {
		A = 111;
		cout << A << endl;
	}
	static int A;
};

int Person::A = 100;

int main() {
	Person p1;
	p1.func();       //1.通过对象访问
	Person::func();  //2.通过类名访问
	return 0;
}

4.6 对象特性—this指针

非静态成员函数被调用时,可能有很多个对象都在调用,为了识别是哪一个对象调用,c++提供了this指针。
this指针是隐含在非静态成员函数中的一种指针,不需要定义可以直接使用,其指向为被调用的成员函数所属的对象。
用途
1.形参和成员变量同名时的区分。
2.在类的非静态成员函数中返回对象本身。
本质
this指针指向不可以修改,本质上是指针常量。
class_name * const this;

class Person {
public:
	Person(int A) {
		this->A = A;  //解决名称冲突
	}
	Person& A_add(Person &p){ //引用方式返回对象本身
		this->A += p.A;  
		return *this; //this指向p2.*this就是p2本身
	}
	int A;
};

int main() {
	Person p1(17);
	Person p2(13);
	p2.A_add(p1).A_add(p1);  //链式编程--p2.A_add返回p2
	cout << p2.A << endl;
	return 0;
}

4.7 对象特性—空指针访问成员函数

原因:下列代码中func中调用了成员变量,其中A前面其实隐含了一个this->,这就导致this为NULL,访问了非法内存,从而导致调用失败。如果func不访问类的成员变量的话,就相当于一个类外函数,可以正常调用。所以很多程序猿会在成员函数里加一个判断。

class Person {
public:
	void func() {
		if (this == NULL) { return; }
		cout << A << endl;  //this->A
	}
	int A = 2;
};

int main() {
	Person* p = NULL;
	p->func();  //调用失败,VS最新的好像不会崩溃了
	return 0;
}

4.8 对象特性—const修饰成员函数

常函数
在成员函数后加const修饰,特性是内部不可以修改成员属性,如果还想修改,需要在成员属性声明的时候加mutable关键字。
常对象
声明对象前加const,常对象只能调用常函数。

常函数原理
前面说过了this指针的本质是指针常量,其指向不可修改,常函数其实是在this指针的基础上使其指向的值也禁止修改,也就是:const class_name * const this;
体现在函数的创建上就是:返回值类型 函数名() const{}

class Person {
public:
	void func() const{
		A += 1;  //错误的,不理智的
		B += 1;  //正确的,可编译的
	}
	void func1(){}
	int A = 2;
	mutable int B;
};

int main() {
	const Person p; //常对象
	p.A = 100;    //不可以修改A
	p.B = 100;    //可以修改B
	p.func1();    //不可以调用非常函数
	return 0;
}

4.9 友元

作用:让一个函数或类可以访问另一个类中的私有成员。
三种类型
1.全局函数友元
2.类友元
3.成员函数友元

class Person;
class woman {
public:
	woman();
	void visit();
	Person* w;
};

class Person {
	friend void gay(Person* p);  //将全局函数列为友元
	friend class dog;            //将类列为友元
	friend void woman::visit();   //成员函数列为友元

public:
	Person() { A = "让我康康"; }

private:
	string A;
};

//为什么一定要放到类外,而且要在Person类后面?
woman::woman() { w = new Person; }
void woman::visit() { cout << w->A << endl; }

class dog {
public:
	void liugou() { cout << p->A << endl; }
	Person* p = new Person;
};

void gay(Person* a) {
	cout << a->A << endl;
}

int main() {
	Person p1;
	gay(&p1);
	dog dog1;
	dog1.liugou();
	woman p2;
	p2.visit();
	return 0;
}

关于注释中的问题:
首先要了解前向声明。可以声明一个类而不定义它,这个声明被称为前向声明,如代码块中的class Person;。
这个时候,即声明之后,定义之前,Person类还是一个不完全类型,只能以有限方式使用,禁止定义该类型对象,只能用于定义指向该类型的指针/引用,或用于声明该类型作为形参类型或者返回值的函数。
因此,需要把woman类中涉及构造Person对象的操作放到类外,并且在Person类定义之后。
建议:干脆把所有类的构造和成员函数都写在类外,统一放到前向声明,类定义后面。

4.10 运算符重载

PS:
1.内置类型运算符无法改变,比如正常+
2.不要滥用运算符重载。

//通过全局函数重载了+
Person operator+ (Person& p1, Person& p2) {
	Person temp;
	temp.B = p1.B + p2.B;
	return temp;
}

//调用
Person a, b, c;
a.B = 10; b.B = 20;
c = a + b; //这里+是重载后的+,实现了不同对象成员属性的加法

//用类内成员函数重载也是一样

//operator<<重载左移运算符(一般用全局函数)
ostream& operator<<(ostream& cout, Person& p) {
	cout << p.B << endl;
	return cout;
}
cout << c.B << endl; //调用默认的<<
cout << c << endl;   //调用重载<<,为了连续使用<<需要链式编程

//重载赋值运算符operator=
//作用是避免默认=带来的浅拷贝析构崩溃问题

//仿函数-函数调用运算符()重载
//类内重载
int operator()(int a, int b) {
	return a + b;
}
//类外调用
Person a;
cout << a(10, 10) << endl;
cout << Person()(1, 2) << endl;  //匿名函数对象Person()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值