回顾C++
目录
4.1 全局函数做友元: friend void func();
例子:一个类Friend可以访问Person中的私有成员age
BUG: c++出现 “no default constructor exists for class”的问题
例子2:Friend访问Building中的私有成员bedroom
4.3 成员函数做友元:friend void Friend::func();
1. 类中成员变量和成员函数分开存储
-
c++中,类内成员变量和成员函数分开储存
-
只有非静态的成员变量才属于类的对象上
class Person
{
int m_A; //非静态成员变量,属于类的对象上
static int m_B; //静态成员变量,不属于类的对象上
void func(){} //非静态成员函数,不属于类的对象上
static void func2(){} //静态成员函数,不属于类的对象上
}
1.1 空对象占用的空间:1字节
相当于用书占位
class Person
{
};
void test()
{
Person p;
cout<<"sizeof p is "<< sizeof(p)<<endl; //1
}
int main()
{
test();
}
原因:
C++编译器会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置
每个空对象应该有一个独一无二的内存地址
1.2 有非静态成员变量的类:根据变量大小
class Person
{
int m_a; //属于类的对象上
};
void test()
{
Person p;
cout<<"sizeof p is "<< sizeof(p)<<endl; //4
}
int main()
{
test();
}
非静态成员变量大小为4,所以类占用的大小为4.
C++类中的内存对齐
C++中的类的对齐的字节,并不是一个定数,而是以类中的成员变量占用的字节数最大的类型作为对齐标准的。
例子1:
class Person
{
char ch;
int m_a; //属于类的对象上
};
void test()
{
Person p;
cout<<"sizeof p is "<< sizeof(p)<<endl; //8
}
int main()
{
test();
}
结果是8,说明以4个字节作为对齐
例子2:
class MyClass
{
private:
char b; // 1
double c; // 8
};
void test()
{
Person p;
cout<<"sizeof p is "<< sizeof(p)<<endl; //16
}
int main()
{
test();
}
结果是16,说明现在的内存对齐的标准又变成了8个字节了
1.3 有静态成员变量的类:静态成员变量不占空间
class Person
{
static int m_a; //静态成员变量,不属于类的对象上
};
int Person::m_a = 0;
void test()
{
Person p;
cout<<"sizeof p is "<< sizeof(p)<<endl; //1
}
int main()
{
test();
}
结果是1(相当于只有一个空类)
1.4 非静态成员函数的类:函数不占用空间
成员函数不占用空间,不属于类的对象上
class Person
{
void func(){}
};
void test()
{
Person p;
cout<<"sizeof p is "<< sizeof(p)<<endl; //1
}
int main()
{
test();
}
1.5 静态成员函数的类:函数不占用空间
1.6 普通继承
class A
{
int a;
};
class B
{
int b;
};
class C : public A, public B
{
int c;
};
结果为12.说明普通继承就是基类的大小加上派生类自身成员大小
1.7 虚拟继承
class A
{
int a;
};
class B
{
int b;
};
class C : virtual public A, virtual public B
{
int c;
};
class D : virtual public A
{
int d;
};
void test2()
{
//A a;
cout<<sizeof(int)<<endl; //4
C c;
cout<<sizeof(c)<<endl; //24
D d;
cout<<sizeof(d)<<endl; //16
}
int main(int argc, char const *argv[])
{
test2();
return 0;
}
当存在虚拟继承时,派生类中会有一个指向虚基类表的指针。所以其大小应为普通继承的大小,再加上虚基类表的指针大小(32位4个字节,上图例子64位8个字节)。
1.8 延申到this指针:
从上面可以看出,因为成员函数只会诞生一份函数实例,所以多个同类型对象会共用一部分代码。那么需要this指针来区分哪个对象在调用自己。
2. this指针
2.1 用途:
- 形参和实参同名时加以区分
- 在非静态成员函数的返回值为对象本身时,使用*this
2.2 链式编程:
class Person
{
public:
Person(int age)
{
this->age = age;
}
Person& addAge(Person& p)
{
this->age += p.age;
return *this;
}
int age; //属于类的对象上
};
void test()
{
Person p(10);
Person p2(20);
cout<< p.addAge(p2).age <<endl; //30
cout<< p.addAge(p2).addAge(p2).age <<endl; //70
}
int main()
{
test();
}
注意:Person& addAge(Person& p)中,
- 参数使用引用的原因:传入地址,节约空间
- 返回值使用引用的原因:相当于返回它的本身,从而可以实现一直addAge。如果不是引用,结果为20.
3. 常函数和常对象
3.1 常函数
一个类中,如果成员函数后面加上const关键字,则该函数成为常函数。
常函数不可以修改成员变量的值。
class Person
{
public:
void printAge() const
{
cout<<"age is "<< m_age <<endl;
}
int m_age;
};
原因:m_age相当于this->m_age,但是this是一个指针常量(可以改变值,但不能改变指向),再加上一个const,导致值也不能改变。
3.2 mutable关键字
如果成员变量前面有mutable关键字,则可以在常函数中修改这个成员变量的值。
class Person
{
public:
void printAge() const
{
m_age += 1;
cout<<"age is "<< m_age <<endl;
}
mutable int m_age;
};
3.3 常对象
在声明一个对象时,前面加上const关键字。
- 常对象中的成员变量不可以被修改,但可以被访问。
- 常对象只能访问被 const 修饰的成员函数
-
mutable修饰的成员变量可以在常对象中被修改
class Person
{
public:
void showPerson() const
{
m_B = 200; //可以修改
}
void func()
{
m_A= 200;
}
int m_A;
mutable int m_B; //加关键字
};
void test()
{
const Person p;
p.m_A = 100; //报错
p.m_B = 300; //可以修改
p.showPerson(); //可以,因为是常函数
p.func(); //报错
}
注意:有些编译器g++中,不能直接声明const Person p
,需要自己写构造函数
#include <iostream>
using namespace std;
class Person
{
public:
Person(int a, int b):m_A(a),m_B(b){}
int m_A;
mutable int m_B; //加关键字
void showPerson() const
{
m_B = 200; //可以修改
}
void func()
{
m_A= 200;
cout<<"haha"<<endl;
}
};
void test()
{
const Person p(10,20);
//p.m_A = 100; //报错
p.m_B = 300; //可以修改
p.showPerson();
//p.func(); //报错
cout<<p.m_A<<endl; //可以访问
cout<<p.m_B<<endl; //可以访问
}
int main()
{
test();
}
4. 友元
让一个函数/一个外部的类访问当前类中的私有成员。
关键字:friend,注意要在当前类中声明该函数/类。
三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.1 全局函数做友元: friend void func();
#include <iostream>
using namespace std;
class Person
{
friend void getAge(Person& p);
public:
Person(int age)
{
this->age = age;
}
private:
int age;
};
void getAge(Person& p)
{
cout<<p.age<<endl;
return;
}
void test()
{
Person p1(10);
getAge(p1);
}
int main()
{
test();
}
4.2 类做友元:friend class Friend;
例子:一个类Friend可以访问Person中的私有成员age
#include <iostream>
#include <string.h>
using namespace std;
class Person
{
friend class Friend;
public:
Person(string name, int age)
{
this->name = name;
this->age = age;
}
public:
string name;
private:
int age;
};
class Friend
{
public:
Friend(string name,Person* person)
{
this->name = name;
p = person;
}
void printAge()
{
cout<<p->name<<endl;
cout<<p->age<<endl;
}
string name;
Person *p;
};
void test()
{
Person p("henry",20);
Friend f("Bill",&p);
f.printAge();
}
int main()
{
test();
}
BUG: c++出现 “no default constructor exists for class”的问题
当自己定义有参构造时,默认的无参构造函数将被覆盖
c++,出现 “no default constructor exists for class”的问题-CSDN博客
例子2:Friend访问Building中的私有成员bedroom
#include <iostream>
#include <string.h>
using namespace std;
class Building
{
friend class Friend;
public:
Building();
private:
string bedroom;
};
Building::Building()
{
bedroom = "br";
}
class Friend
{
public:
Friend();
void visit(); //参观函数,访问Building中的属性
Building * building;
};
Friend::Friend()
{
building = new Building;
}
void Friend::visit()
{
cout<<"visiting: "<<building->bedroom<<endl;
}
void test()
{
Friend f;
f.visit();
}
int main()
{
test();
}
4.3 成员函数做友元:friend void Friend::func();
例子:
#include <iostream>
#include <string.h>
using namespace std;
class Person;
class Friend
{
public:
Friend(string name, Person* p);
void getAge();
string name;
Person *p;
};
Friend::Friend(string name, Person* p)
{
this->name = name;
this->p = p;
}
class Person
{
friend void Friend::getAge();
public:
Person(string name, int age);
string name;
private:
int age;
};
Person::Person(string name, int age)
{
this->name = name;
this->age = age;
}
void Friend::getAge()
{
cout<<p->name<<endl;
cout<<p->age<<endl;
}
int main(int argc, char const *argv[])
{
/* code */
Person p("Henry", 10);
Friend f("Bill",&p);
f.getAge();
return 0;
}
注意:void Friend::getAge()必须定义在Person结束后,不然会报错:
error: invalid use of incomplete type 'class Person'
cout<<p->name<<endl;
以及
error: invalid use of incomplete type 'class Person'
cout<<p->age<<endl;
5. 继承
5.1 基本语法
class 子类: 继承方式 父类
5.2 三种继承方式
public,protected,private
此时如果有另一个class C无论以何种方式继承B,都无法获取B类的a,b,c元素。
5.3 继承时构造析构顺序以及同名成员的处理方式
5.3.1 结论
直接访问(用.)则是子类自身的成员变量/函数,如果要访问基类,需要加上作用域运算符::
5.3.2 同名成员变量
5.3.3 同名成员函数
如果子类中出现和父类同名的成员函数,子类会隐藏掉父类所有的同名成员函数。
注意,与函数重载不同,因为作用域不一样。
函数重定义/隐藏(之后继续补充)
- 不在同一个作用域(分别位于派生类与基类) ;
- 函数名字相同;
- 返回值可以不同;
- 参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆)
- 参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout<<"Base gouzao"<<endl;
m_A = 10;
}
void getNum()
{
cout<<"m_A is: "<<m_A<<endl;
return;
}
~Base()
{
cout<<"Base xigou"<<endl;
}
int m_A;
};
class Child: public Base
{
public:
Child()
{
cout<<"Child gouzao"<<endl;
m_A = 20;
}
int getNum()
{
cout<<"m_A is: "<<m_A<<endl;
return m_A;
}
~Child()
{
cout<<"Child xigou"<<endl;
}
int m_A;
};
void test()
{
Child c;
cout<< c.m_A <<endl; //20
cout<< c.Base::m_A <<endl; //10
int b = c.getNum(); //m_A is: 20
cout<< b <<endl; //20
c.Base::getNum(); //m_A is: 10
}
int main(int argc, char const *argv[])
{
/* code */
test();
return 0;
}
输出:
Base gouzao
Child gouzao
20
10
m_A is: 20
20
m_A is: 10
Child xigou
Base xigou
5.3.4 同名静态成员变量
回顾静态成员变量:所有类共享同一个变量,类内声明类外初始化,编译阶段分配内存,两种访问方式(类名和对象)
继承中处理同名静态成员变量与正常的成员变量方法一致。
唯一的额外写法:获取子类的基类中的某个成员变量
Child::Base::m_A;
#include <iostream>
using namespace std;
class Base
{
public:
static int m_A;
};
int Base::m_A = 10;
class Child: public Base
{
public:
static int m_A;
};
int Child::m_A = 20;
void test()
{
Child c;
cout<<c.m_A<<endl; //20
cout<<c.Base::m_A<<endl; //10
cout<<Child::m_A<<endl; //20
cout<<Child::Base::m_A<<endl; //10
cout<<Base::m_A<<endl; //10
}
int main(int argc, char const *argv[])
{
/* code */
test();
return 0;
}
5.3.5 同名静态成员函数
回顾静态成员函数:所有类共享同一个函数,只能访问静态成员变量
继承中同名静态成员函数方法和静态成员变量类似。
Child c;
c.getNum();
c.Base::getNum();
Child::getNum();
Base::getNum();
Child::Base::getNum();
5.4 多继承
语法:
class 子类: 继承方式 父类1, 继承方式 父类2, ...
多继承容易导致同名成员出现,所以实际中不建议使用。
5.4.1 二义性
两个父类有同名的成员,此时子类访问时,需要作用域运算符来加以区分
5.5 菱形继承
有两个类继承同一个基类,又有一个派生类多继承这两个类。
5.5.1 出现的问题:
派生类会出现二义性(无论是成员函数还是成员变量)(需要加作用域区分),以及派生类会重复继承一些数据(资源浪费)。
5.5.2 虚继承
解决菱形继承的问题
语法:
class A {};
class B: virtual public A {};
class C: virtual public A {};
class D: public B, public C {};
B和C虚继承,A为虚基类。D中相同属性只会保留一份数据。
虚基类指针:vbptr,指向对应的虚基类表:vbtable
#include <iostream>
using namespace std;
class A
{
public:
int m_A;
};
class B: public A
{
public:
int m_B;
};
class C: public A
{
public:
int m_C;
};
class D: public B, public C
{
public:
int m_D;
};
int main(int argc, char const *argv[])
{
/* code */
D d;
// d.m_A = 10; //报错,不明确,因为B,C都有该m_A属性
d.B::m_A = 10;
// d.A::m_A = 20; //报错,不明确,孙子无法直接访问基类
d.C::m_A = 20;
cout<<d.B::m_A<<endl; //10
cout<<d.C::m_A<<endl; //20
return 0;
}
参考:
1. 重写/重载(覆盖)/重定义(隐藏):
https://www.cnblogs.com/tanky_woo/archive/2012/02/08/2343203.html