c++_note_3_面向对象基础

这一部分只包括零散的面向对象知识点,封装、继承、多态等重要特性在另一篇笔记里记录。

3.面向对象基础

3.1 类

c中,数据和函数没有关联,是分开声明的。
c++通过抽象数据类型(ADT),在类中定义数据和函数实现绑定。

类封装了属性和方法
简单的方法也会自动内联编译
3种访问权限这里不再赘述。
c + + 类 { 成 员 数 据 { s t a t i c n o n s t a t i c 成 员 函 数 { s t a t i c n o n s t a t i c v i r t u a l c++类\begin{cases} 成员数据 \begin{cases} static \\nonstatic \end{cases} \\\\成员函数 \begin{cases} static \\nonstatic \\virtual \end{cases} \end{cases}\\ c++{staticnonstaticstaticnonstaticvirtual

c++类的成员变量和成员函数是分开存储的。

  • 普通成员变量存储于对象中,类似struct的内存布局和字节对齐方式。
  • 静态成员变量存储于全局数据区中。

成员函数存储于代码段中。

  • 普通成员函数通过this指针区分对象。
  • 静态成员函数没有this指针。
#include "iostream"

using namespace std;

class Circle{
public:
//member function 
	void setR(double m_r){
		r = m_r;
	}
	double getR(){
		return r;
	}
	double getS(){
		return 3.14f*r*r;
	}
private:
	double r;
};


void outputS(Circle &c){
    //automatically inlined
	cout<<c.getS()<<endl;
}

int main()
{
	double r=0,s=0;
	Circle c1;
	
	cout<<"input the radius:";
	cin >> r;
	cout<<"radius:"<<r<<endl;
	
	c1.setR(r);
	cout<<endl;
	cout<<"object's radius:"<<c1.getR()<<endl;
//	cout<<"object's radius:"<<c1.r<<endl;
	
	cout<<"c1.getS():"<<c1.getS()<<endl;
	cout<<"(&c1)->getS():"<<(&c1)->getS()<<endl;
	//注意这里可以用指针调用成员函数
    
	outputS(c1);
	
	cin.get();
	return 0;
}

对比struct

  • struct的属性默认为public;
  • class的属性默认为private,这也是和java的不同之处。
//C++类
class MyClass{
public:
	MyClass(int a){
		this->a = a;  //usage of "this"
	}
 	int getA(){
 		return a;
	} 
	static void print(){
		//cout<<this->a<<endl; //error,静态方法不能调用普通成员变量. 
		cout<<"print something."<<endl; 
	}
private:
	int a;
};
/
//c的底层实现
struct MyClass{
	int a;
} 
void MyClass_init(MyClass* const this, int m_a){
	this->a = m_a;
}

int MyClass_getA(MyClass* const this){
	return this->a;
}

static void MyClass_print(){
	printf("print something.\n");
}
//
MyClass c(1);  //MyClass_init(&c, 1)
c.getA();      //MyClass_getA(&c)
MyClass::print(); //MyClass_print()

this

成员函数可通过this返回引用,也可通过this引用。

#include<iostream>
using namespace std;

class MyClass{
public:
	MyClass(int a){
		this->a = a;
		cout<<"construct a="<<a<<endl;
	}
	MyClass& retref(){
		this->a += 1;
		return *this;	
	}
	MyClass (const MyClass& p){
		cout<<"copy constructor"<<endl;
		this->a = p.a;
	}
	~MyClass(){
		//cout<<"destruct  a="<<a<<endl;  
		this->print(); 
	}
	void print(){
		cout<<"destruct  a="<<a<<endl;
	} 
	
private:
	int a;
};

int main(int argc, char *argv[])
{
	MyClass c1(1);
	MyClass c2 = c1.retref();
	cin.get();
	return 0;
}

3.2 构造和析构

3.2.1 构造函数

  • 函数名为类名
  • 可以有参数
  • 无返回类型声明
  • 一般编译器自动调用,有时候也需要手动调用。

构造函数分为:

  • 默认构造函数
  • 普通(无参、有参)构造函数
  • 赋值构造函数,即提供默认参数,eg, (int = 1)

构造函数通常重载。如果没有自己定义有参或赋值构造函数,编译器提供默认的无参构造函数和赋值构造函数。也就是说,只要自己定义了构造函数,那就必须用,否则无法创建对象。

3.2.1.1 copy构造函数

myclass(const myclass& c){}

赋值构造函数,也叫copy构造函数,它用一个对象去初始化另一个对象,有4种调用情况:

  • MyClass a = b;
  • MyClass a(b);
  • 函数形参为该类对象
  • 函数返回匿名对象

所以说,赋值构造函数是对象正确初始化的保证,必要时(浅拷贝)得手工编写。

注意,引用作为参数时,并不调用copy构造函数。

3.2.1.2 explicit关键字

关键字explicit可以抑制内置类型隐式转换.

MyClass(int a)
{
    this->a = a;
}
MyClass c = 1;	//不报错

explicit MyClass(int a){}, 这样,就可以禁止隐式类型转换了.

3.2.2 析构函数

  • ~CLASSNAME();
  • 没有参数也没有返回类型声明
  • 对象销毁时编译器自动调用

某函数返回一个匿名对象:

  • 用返回的匿名对象初始化同类型对象,则不析构匿名对象。
  • 用返回的匿名对象赋值同类型对象,则马上析构匿名对象。
#include <iostream>
using namespace std;

class Person{
public:
	Person(){
		index++;
		cout<<index<<": no-arg constructor"<<endl;
	}
	Person(int a){
		index++;
		flag = a;
		cout<<index<<": constructor-"<<flag<<endl;
	}
	Person(const char* s,int a){
		index++;
		flag = a;
		cout<<index<<": "<<s<<endl;
	}
	Person(const Person& p){
		//copy构造函数
		index++;
		cout<<index<<": copy constructor."<<endl;
//		flag = p.flag;
		flag = p.getFlag();//调用常量成员函数 
	}
	
	~Person(){
		cout<<index<<": destructor--"<<flag<<endl;
		index--;
	}
	
	int getFlag() const{ 
	/*
	   const: 常量成员函数,不能修改无mutable修饰的成员变量 。 
	   编译器将对这些const成员函数进行检查,
	   如果确实没有修改对象值的行为,则检验通过。 
    */ 
    //	flag += 1;
		return flag;
	}
	
private:
	int flag;
	//mutable int flag;
	static int index;
protected:
};

int Person::index = -1;

Person test(Person p){ //调用copy构造函数
/* 
	利用栈和堆的知识理解test() 
*/ 
	return p;  
	//这里会发现再次调用copy构造函数,用p构造一个堆上的匿名对象并返回
	//然后栈上的p被销毁 
}

void func(Person &c){}

int main(int argc, char *argv[])
{
	printf("    Person p0(0);\n    Person p1(1);\n");
	Person p0(0);  //栈中,先创建,后释放。会看到输出 1  0 
	Person p1(1); 
	
	cout<<endl;
	printf("    Person p2 = (0,1,2);\n");
	Person p2 = (0,1,2); //c++将等号功能增强 
	
	//调用无参构造函数
	cout<<endl;
	printf("    Person p3; \n    Person p4 = Person();\n");
	Person p3; 
	Person p4 = Person();
	//下面是2种错误的无参构造函数调用方法 
	//Person p4(); 
	//Person p4 = Person;
	
	//手动调用构造函数,将产生匿名对象
	cout<<endl;
	printf("    Person p5 = Person(\"manual call\",5);\n    p4 = p5;\n");
	Person p5 = Person("manual call",5);  //手动调用
	p4 = p5; //copy赋值,注意没有调用copy构造函数。
	
	//调用copy构造函数初始化
	cout<<endl;
	printf("    Person p6 = p2;\n    Person p7(p2); \n"); 
	Person p6 = p2;
	Person p7(p2); 
	
	cout<<endl; 
	cout<<"    Person p8 = test(p3)"<<endl;
	Person p8 = test(p3); //用返回的匿名对象初始化p9,不析构匿名对象。
	cout<<endl; 
	cout<<"    p8 = test(p3)"<<endl;
	p8 = test(p3); //赋值,然后析构匿名对象 
	cout<<endl;
    
    cout<<endl;
	printf("    func(p1)\n");
	func(p1);//没有调用copy构造函数 
	 
	cin.get();
	return 0;
}
/*
    Person p0(0);
Person p1(1);
0: constructor-0
1: constructor-1

    Person p2 = (0,1,2);
2: constructor-2

    Person p3;
Person p4 = Person();
3: no-arg constructor
4: no-arg constructor

    Person p5 = Person("manual call",5);
p4 = p5;
5: manual call

    Person p6 = p2;
    Person p7(p2);
6: copy constructor.
7: copy constructor.

    Person p8 = test(p3)
8: copy constructor.
9: copy constructor.
9: destructor--1978168753

    p8 = test(p3)
9: copy constructor.
10: copy constructor.
10: destructor--1978168753
9: destructor--1978168753

    func(p1)

8: destructor--1978168753
7: destructor--2
6: destructor--2
5: destructor--5
4: destructor--5
3: destructor--1978168753
2: destructor--2
1: destructor--1
0: destructor--0
*/

3.3 浅拷贝

若类里有指针(char*p),
MyClass a = b;
a.p = b.p,它们指向堆的同一地址
这是浅拷贝.

编译器提供的默认析构函数会因为浅拷贝遇到free(野指针)的问题
这时就需要手工编写拷贝构造函数,让它变为深拷贝。

MyClass(const Myclass &arg){
	int len = arg.len;
	p = (char*)malloc(len + 1);
	strcp_s(p, len + 1, arg.p);
}

但是,对于a = b,等号赋值不调用copy构造函数,仍然是浅拷贝,同样导致野指针问题。这时要重载等号操作符,重载操作符为下一部分内容。

3.4 初始化列表

类B里嵌套类A时,A该如何初始化?
思考一下会明白,即使提供合适的构造函数也无法初始化。
所以c++提供了初始化列表:
与初始化列表顺序无关,构造顺序为定义顺序,析构顺序相反。

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

class Name{
public:
	Name(const char *s){
		int len = strlen(s);
		m_name = (char*)malloc(len+1);
		strcpy(m_name,s);
		cout<<"construct A"<<endl;
	}
	Name(const Name &s){
		int len = strlen(s.m_name);
		m_name = (char*)malloc(len+1);
		strcpy(m_name,s.m_name);
		cout<<"construct A"<<endl;
	}
	~Name(){
		if(m_name != NULL){
			free(m_name);
			m_name = NULL;
		}
		cout<<"desconstruct Name"<<endl;
	}
	
	void getAddr(){
		cout<<"addr:"<<&m_name<<endl; 
	} 
private:
	char *m_name;
protected:

};

class Person{
public:
    //initialization list
	Person(int m_age,const char* s,int m_sex):name(s),sex(m_sex){
		age = m_age;
		//sex = m_sex; //error
		cout<<"construct B"<<endl;
	}

	~Person(){
		cout<<"destruct Person"<<endl;
	}
private:
	int age;
	Name name;
	const int sex; //常量必须按上面那样初始化 
protected:

};

int main(int argc, char *argv[])
{
	/*
 		调试观察构造顺序 
	*/
	Person p(3,"foobar",1);
	
	Name n1("tom");
	Name n2 = n1;
	n1.getAddr();
	n2.getAddr();

	cin.get();
	return 0;
}

3.5 对象的动态建立和释放

new和delete运算符,可替代malloc和free函数。它们可以交叉使用。
它们的区别是,new、delete可以执行构造和析构函数,而malloc和free不执行。

注意,是运算符,不是函数,所以效率高。

指针变量 = new 类型(常量);
delete 指针变量;
类型可以是基础类型,数组,类。

int *p1 = new int;
int *p2 = new int(1);
int *p3 = new int[3];
p3[0] = 1;
delete p1,p2;
delete [] p3;

3.6 static

静态成员提供了同类对象的共享机制

#include<iostream>
using namespace std;
class MyClass{
public:
	int a;
	const static double foo = 1;
	static int bar;
	 
	static void print(){
		//cout<<a<<endl;  //error,静态方法不能调用普通成员变量. 
		cout<<"print something."<<endl; 
	}
};

int MyClass::bar = 2;     //不是简单赋值,必须有int请求分配内存。

int main()
{
	MyClass c1,c2;
	cout<<"c1.foo:"<<c1.foo<<endl;
	cout<<"c1.bar:"<<c1.bar<<endl;
	cout<<endl;
    
	c1.bar = 3;
	cout<<"c1.bar:"<<c1.bar<<endl;
	cout<<"c2.bar:"<<c2.bar<<endl;
	cout<<"MyClass::bar :"<<MyClass::bar<<endl;

	cout<<endl;
	MyClass::print();
	c1.print(); //对象也能调用 
	
	cin.get();
	return 0;
}
/*
c1.foo:1
c1.bar:2

c1.bar:3
c2.bar:3
MyClass::bar :3

print something.
print something.
*/

3.7 常量成员函数

class MyClass{
public:
	int getFlag() const{
//	const int getFlag(){
//	int const getFlag(){
	/*
	   常量成员函数,不能修改非mutable修饰的成员变量
	   const修饰的是隐藏的this指针所指的空间
	   MyClass *const this --> const MyClass *const this
	   编译器将对这些const成员函数进行检查,
	   如果确实没有修改对象值的行为,则检验通过。
    */ 
    //		flag += 1;
		return flag;
	}
	
private:
	int flag;
	//mutable int flag;
};

3.8 友元类和友元函数

友元函数
与声明所在的访问权限位置无关
可以用来修改类的私有属性

#include<iostream>
using namespace std;

class T{
private:
	int a;
	friend void modifyA(T* p,int a);
public:
	T(int a){
		this->a = a;
	}
	void print(){
		cout<<this->a<<endl;
	}
};

void modifyA(T* p,int a){
	p->a = a;
} 

int main(int argc, char *argv[])
{
	T t(1);
	modifyA(&t,2);
	t.print();
	return 0;
}
/*
2
*/

友元类
若B类是A类的友元类,则B类的所有成员函数都是A类的友元函数,即B中可以访问A的私有成员。
友元类通常作为一种对数据操作或类之间传递消息的辅助类。
主要用途是在java的反射机制里改变私有属性。
友元类破坏了封装特性,其实为了避免分析汇编而准备的一个后门。

#include<iostream>
using namespace std;

class A{
public:
	friend class B;
	A(int a){
		this->a = a;
	}
private:
	int a;
};

class B{
public:
	B(int a):aB(a){}
	void setA(int a){
		aB.a = a;
	}
	void print(){
		cout<<aB.a<<endl;
	}
private:
	A aB;
};

int main(int argc, char *argv[])
{
	B b(1);
	b.print();
	b.setA(2);
	b.print();
	
	cin.get();
	return 0;
}
/*
1
2
*/

3.9声明和定义分离–工程示例

/*
   Person.h
*/
#pragma once


//#ifndef __MYCLASS_H_
//#define __MYCLASS_H_

class Person{
private:
	int age;
public:
	int getAge();
	void setAge(int a);
};

//#endif

/*
   Person.cpp
*/
#include "Person.h"
int Person::getAge(){
	return age;
}
void Person::setAge(int a){
	age = a;
}


/*
   main.cpp
*/
#include <iostream>
using namespace std;
#include "Person.h"
int main(int argc, char *argv[])
{
	Person p;
	p.setAge(2);
	cout<<p.getAge()<<endl;
	return 0;
}


#pragma once,意思是只包含一次,避免同一个文件被include多次而重复定义。
优点:不会因宏名碰撞导致头文件明明存在,编译器却找不到声明。
缺点:如果某个头文件有多份拷贝,此方法不能保证它们不被重复包含。
另一种方式为
#ifndef __MYCLASS_H_
#define __MYCLASS_H_
#endif
优缺点相反

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值