文章目录
这一部分只包括零散的面向对象知识点,封装、继承、多态等重要特性在另一篇笔记里记录。
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++类⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧成员数据{staticnonstatic成员函数⎩⎪⎨⎪⎧staticnonstaticvirtual
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
优缺点相反