- 类中允许存在变量与函数;
- 类定义末尾要加
;
; - 类中变量称为成员变量(member_data),函数称为成员函数(member_function)或方法(method);
- 类名遵循大驼峰命名规则。
class Name {
public: // 访问权限
int m_a; // 成员函数
void func() { // 方法
std::cout << "hello world" << std::endl;
}
};
使用.
运算符直接调用类内成员
#define PI 3.14
class Circle {
// 访问权限
public: // 公共权限
// 属性
int m_r; // 半径
// 行为
double calculateZC() {
return 2 * PI * m_r;
}
};
int main() {
// 通过圆类 创建具体的圆(对象)
// 实例化 通过一个类 创建一个对象
Circle cl;
// 给圆对象的属性进行赋值
cl.m_r = 10;
std::cout << "circumference: " << cl.calculateZC() << std::endl;
}
以下所讨论的成员均不包括静态成员,有关友元继承的内容后面会学到
public
公共权限
类内,子类,友元,类外都可以访问class A { public: int m_a; void func() { m_a = 0; } }; int main() { A obj; obj.m_a = 1; // legal }
protected
保护权限
类内,子类,友元可以访问 类外不可以访问class A { protected: int m_a; void func() { m_a = 0; } }; int main() { A obj; obj.m_a = 1; // illegal }
private
私有权限
类内,友元可以访问 子类,类外不可以访问class A { private: int m_a; void func() { m_a = 0; } }; int main() { A obj; obj.m_a = 1; // illegal }
总结如下表:
访问权限 | 类内&友元 | 子类 | 类外 |
---|---|---|---|
public |
yes | yes | yes |
protected |
yes | yes | no |
private |
yes | no | no |
class
&struct
struct默认公共权限
class默认私有权限class C1 { int m_A; // 默认权限 private }; struct C2 { int m_A; // 默认权限 public }; int main() { C1 c1; c1.m_A=100; // error C2 c2; c2.m_A = 100; // 100 }
只有在创建对象时可以手动调用构造函数,其余时候不可以调用构造和析构函数
如果已有用户声明 (user-declared) 的构造函数,编译器生成的构造函数代码会接安插在用户写的代码 (explicit user code) 前
- 默认构造函数只在它需要的时候被编译器产生出来
- 如果没有用户定义的默认构造函数或是编译器生成的默认构造函数,默认构造函数为隐式的无用默认构造函数 (implicit trivial default constructor)
语法:
ClassName()
在类对象被创建时自动执行且只执行一次;函数可以有参数,可以重载
class A {
public:
A();
};
语法:~ClassName()
在类对象被销毁前自动执行且只执行一次;函数不可以有参数,不可以重载
class A {
public:
~A();
};
class A {
public:
A() {
std::cout << "A_constructor" << std::endl;
}
A(int a) {
m_a = a;
std::cout << "A_constructor" << std::endl;
}
int m_a;
};
- 拷贝构造函数只在它需要的时候被编译器产生出来
- 如果没有用户定义的拷贝构造函数或是编译器生成的拷贝构造函数,拷贝构造函数为隐式的无用默认拷贝函数 (implicit trivial copy constructor)
- 自己实现的拷贝构造函数的形参类型必须为引用,一般加上
const
- 不加引用会导致函数变成无限递归函数
class A {
public:
A() {
std::cout << "A_constructor" << std::endl;
}
A(const A& obj) {
m_a = obj.m_a;
std::cout << "A_constructor" << std::endl;
}
int m_a;
};
//括号法
A obj1; //普通构造
A obj2(10); //有参构造
A obj3(obj2); //拷贝构造
//显式法 以匿名对象为桥梁
A obj1; //普通构造
A obj2 = A(10); //有参构造 A(10)为匿名对象
A obj3 = A(obj2); //拷贝构造
A(obj1); // error
//隐式法 发生隐式类型转换
A obj1 = 10; //有参构造
A obj2 = obj1; //拷贝构造
当有参构造函数只有一个参数时,且传入参数符合参数类型声明,调用该函数发生隐式类型转换时,该构造被称为转换构造函数 (converting constructors)
注意
- 调用默认构造函数时不要加(),否则编译器会认为是函数声明
- 不要利用拷贝构造函数初始化匿名对象而无其他对象接管,否则编译器会认为
A(obj3)
<=>A obj3
为对象声明(line10)
A obj1(A(1));
// obj2(1); // 此行仅用于创建被拷贝对象
A obj3(A(obj2));
// 在C++11中,为了防止括号产生的歧义,可以这样写
A obj3{A(obj2)};
等价于
// 这里的等号不会调用拷贝构造
A obj1 = A(1);
A obj3 = A(obj2);
- 匿名对象是构造函数的一个桥梁
- 当匿名对象出现在等号右边时会被左边的对象接管,会省去基于等号的拷贝构造
- 当匿名对象出现在等号左边,就会很快被释放掉
- 显式调用其实就是在类外调用了类的构造函数,没有拷贝构造过程
- 如果等号右边的匿名对象是由一个非对象参数构造,调用了有参构造
- 如果右边的匿名对象是由一个对象构造,调用了拷贝构造
class A {
public:
A(int x){}
A(const char* pString){}
}
int main() {
A obj1 = B(5);
A obj2 = B("a");
A obj3 = 5; // => A obj3 = A(5);
A obj4 = "a"; // => A obj4 = A("a");
A obj5 = 'a'; // => A obj5 = A(97);
}
添加explicit
关键字禁用A(int x)
函数的隐式转换
class A {
public:
explicit A(int x){}
A(const char* pString){}
}
int main() {
A obj1 = B(5);
A obj2 = B("a");
A obj3 = 5; // => A obj3 = A(5);
A obj4 = "a"; // => A obj4 = A("a");
A obj5 = 'a'; // => A obj5 = A("a");
}
赋值先后顺序也如下
- 定义时赋值(C++11)
class A {
public:
int m_A = 1;
int m_B = 2;
int m_C = 3;
};
- 初始化列表
class A {
public:
A(int a, int b, int c)
: m_A(a), m_B(b), m_C(c) {}
int m_A;
int m_B;
int m_C;
};
int main() {
A obj(1, 2, 3);
}
- 构造函数赋值
class A {
public:
A(int a, int b, int c) {
m_A = a;
m_B = b;
m_C = c;
}
int m_A;
int m_B;
int m_C;
};
初始化顺序
class A {
public:
A(int a)
: m_a(a) { m_a = 3; }
int m_a = 1;
};
int main() {
A obj(2);
std::cout << obj.m_a << std::endl; // output: 3
}
p1在进行有参初始化时,在堆区申请了一个空间,p1的height指针就指向这个空间;
p2在进行拷贝初始化时使用的是编译器提供的浅拷贝;
浅拷贝是对成员变量的简单赋值,所以p2的height指针=p1的height指针,即两个height指针指向堆区的同一址;
函数test结束后,p1和p2把同一个空间释放了两次,程序崩溃
class Person {
public:
Person() {
std::cout << "default constructor" << std::endl;
}
Person(int age, int height) {
std::cout << "pram constructor" << std::endl;
m_Age = age;
m_Height = new int(height);
}
Person(const Person& p) {
std::cout << "copy_construction" << std::endl;
m_Age = p.m_Age;
// 浅拷贝
m_Height = p.m_Height;
}
// 析构函数 将堆区开辟的数据进行释放
~Person() {
delete m_Height;
m_Height = nullptr;
std::cout << "destructor" << std::endl;
}
int m_Age;
int* m_Height;
};
void test() {
Person p1(18, 160);
std::cout << "p1 age:" << p1.m_Age << " height:" << *p1.m_Height << std::endl;
// 注意 .运算符优先级高于*
Person p2(p1);
std::cout << "p2 age:" << p2.m_Age << " height:" << *p2.m_Height << std::endl;
// error
}
class Person {
public:
Person() {
std::cout << "default constructor" << std::endl;
}
Person(int age, int height) {
std::cout << "pram constructor" << std::endl;
m_Age = age;
m_Height = new int(height);
}
// 重写拷贝构造函数 解决浅拷贝的问题
Person(const Person& p) {
std::cout << "copy_construction" << std::endl;
m_Age = p.m_Age;
// 深拷贝
m_Height = new int(*p.m_Height);
}
// 析构函数 将堆区开辟的数据进行释放
~Person() {
delete m_Height;
m_Height = nullptr;
std::cout << "destructor" << std::endl;
}
int m_Age;
int* m_Height;
};
void test() {
Person p1(18, 160);
std::cout << "p1 age:" << p1.m_Age << " height:" << *p1.m_Height << std::endl;
Person p2(p1);
std::cout << "p2 age:" << p2.m_Age << " height:" << *p2.m_Height << std::endl;
}
edit Serein