首先我要说的是我们定义了类,只是告诉编译器怎么去创建对象,但是不会自动创建一个对象出来
怎么创建类
我们先了解一下相关术语
类成员:在类里声明或定义的所有东西
类数据成员:在类里声明或定义的变量
类方法:在类里声明或定义的函数
c++用class和struct关键字来创建类,创建格式是
class 类名
{
类数据成员,类方法
...
};//不要忘记了分号
或者
struct 类名
{
类数据成员,类方法
...
};//不要忘记了分号
以下是一个简单的C++类的示例:
class AA{
public:
// 成员函数的声明和定义
void A(int a_);
private:
// 成员变量
int a;
};
// 成员函数的定义
void AA::A(int a_) {
a = a_;
}
在上面的示例中,我们定义了一个名为AA的类,它有一个私有的成员变量a和一个公有的成员函数AA。成员变量a是私有的,只能在类的内部访问;而成员函数A是公有的,可以从类的外部调用。
通过类的定义,我们可以创建AA的对象,并通过对象来访问成员变量和成员函数。
可能我们会有一点懵啊,类?对象?public?private?什么玩意?
别急啊,我们一一解答
如何访问类成员
我们可以用成员访问运算符点(.)来访问类对象成员
例如:
AA A;//创建一个AA类的对象
A.A(22);调用A对象的成员函数
在这个例子中,我们创建了一个AA的对象A,并通过对象调用成员函数A()。
类和对象的区别
我们先就代码而言来区分类和对象啊
class AA
{
......
}
这时上面这个AA就是一个类
AA a;
上面这a就是一个对象
你会发现这有点像结构体哈,事实确实如此
访问控制(public和private)
C++中的类成员有三种访问控制,分别是私有访问(private),保护访问(protected)和公有访问(public)。
- 私有访问(private):私有成员只能在类内部访问,对外部是不可见的。这意味着,私有成员不能被类的对象直接访问,也不能被类的派生类访问。私有成员通常用于实现类的内部细节,对外部隐藏。可以通过公有成员函数来间接访问私有成员。
- 保护访问(protected):保护成员可以在类内部访问,也可以在派生类中访问,但对外部是不可见的。保护成员通常用于在派生类中访问和修改基类的成员。
- 公有访问(public):公有成员可以在类内部访问,也可以在外部通过类的对象访问。公有成员通常用于类的接口,提供给外部使用。
可以使用访问说明符(access specifier)来指定成员的访问控制,如下所示:
class AA {
public:
// 公有成员
protected:
// 保护成员
private:
// 私有成员
};
需要注意的是,访问控制是应用于类的成员级别,而不是类级别。也就是说,访问控制只对成员的访问有效,对类本身的访问没有影响。
我们这里就先介绍private和public,因为protected是用于类继承的,我们这里暂时用不到。
关键字private和关键字public描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或者友元函数)来访问类对象的私有成员。
我们可以举个例子
#include<iostream>
using namespace std;
class AA
{
private://私有部分
int a = 1;
public://公有部分
int b = 2;
void H()
{
cout << a << endl;
}
};
int main()
{
AA a;
cout << a.b << endl;//用点来访问类对象的公有部分,结果是2
a.H();//打印出1,类对象的私有数据只能被公有方法(或友元函数)访问
cout<<a.a<<endl;//会报错,类外不能直接访问类对象的私有成员
}
用点来访问类对象的公有部分没有一点问题,
类对象的私有部分只能由类对象的公有成员函数(或者友元函数)访问
有人可能想问了啊,难道类的私有成员函数就不可以访问类的私有成员吗?还有友元函数是个什么东东?别急,掘根这就为大家解惑!
首先,类的私有成员函数就可以访问类的私有成员,但是我们不建议将函数设置为私有的,除了一些特殊情况,因为使用起来很麻烦
我们可以看个例子
#include<iostream>
using namespace std;
class AA
{
private:
int a = 1;
void H()//私有成员函数
{
cout << a << endl;
}
public:
int b = 2;
void dH()
{
H();//公有成员函数调用私有成员函数
}
};
int main()
{
AA a;
a.dH();
}
私有成员函数只能被公有成员函数调用,这就显得调用私有成员函数很麻烦噢!!
因此我们通常将数据成员放在private部分,将成员函数放在public部分
其次,友元函数大家可以自己先去了解了解,我们这篇博客不会讲它,要是感兴趣的话,可以去看看其他博主的文章或者等掘根后续的更新
类的默认访问权限
类的默认默认访问权限分为两种
- class默认为private
- struct默认为public
class
在class创建的类中,我们可以不必在类定义中使用关键字private,因为这是类对象的默认访问控制
大家可能不懂啊,我们举个例子
#include<iostream>
using namespace std;
class AA
{
int a = 1;
};
int main()
{
AA W;
cout<<W.a;//编译器会报错
}
我们可以看到啊,编译器报错了,这说明类对象把a变量默认为私有的了!!!
struct
#include<iostream>
using namespace std;
struct AA
{
int a = 1;
};
int main()
{
AA W;
cout<<W.a;//没问题
}
我们发现这完全没有问题
类和结构
我们学了类,发现类和结构的定义格式和使用方法是类似的啊。
c++通常用类来实现类描述,而把结构限制为只表示存粹的数据对象
它们的默认访问权限不同
类对象的默认访问权限是private,而结构的默认访问权限是public
除了这个,没有其他任何不同
实现类成员函数
一般情况下,我们在类里面只声明函数原型,不会给定义,这时我们就需要在类外给成员函数定义。那怎么做呢?
在类外定义成员函数时,使用作用域运算符(::)来标识函数所属的类
我们可以看个例子
#include<iostream>
using namespace std;
class AA
{
public:
void dH();//类成员函数声明
};
void AA::dh()//注意使用限定的名称
{
cout<<"定义成功“<<endl;
}
int main()
{
AA a;
a.dH();
}
这样子我们也是成功在AA类外定义的AA类的dH()函数
那有人就想问了啊
如果我要想在类内使用dH函数,需不需要作用域解析运算符(::)?
答案是不用的,因为标识符dH在AA类里具有类作用域,AA类的其他类成员函数不必使用作用域解析运算符,就可以使用dH()方法,因为它们属于同一个类的
其次啊,类成员函数可以直接访问类的私有成员
内联方法
如果对内联函数不熟悉的,可以看看这篇博客http://t.csdnimg.cn/FB3kh
其定义位于类声明中的函数都将自动成为内联函数,类声明通常将短小的成员函数作为内联函数
我们可以举个例子
class AA
{
public:
void D()
{
cout<<"D是内联函数"<<endl;
}
};
如果愿意,也可以在类声明之外定义成员函数,并使其成为内联函数,只需在类实现部分定义函数时使用inline限定符即可
举个例子
class AA
{
public:
void A();
};
inline void AA::A()
{
cout<<"A是内联函数"<<endl;
}
内联函数的特殊规则要求在每个使用它们的文件里都对其进行定义。
因此,我们应该将内联定义放在定义类的头文件中
方法使用哪个对象
首先,我想说,我们所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员,但同一个类的所有对象共享同一组类方法,即无论创建多少个对象,每种方法只有一个副本
举个例子
class AA
{
public:
void cc():
...
};
...
int main()
{
AA a,b:
a.cc();//cc函数使用a的数据成员
b.cc();//cc函数使用b的数据成员
...
}
a,b各有自己的数据成员,但是它们共用同一组类方法
这时类方法该使用哪个对象的数据呢?
自然是哪个对象调用的类方法就用谁的数据喽
类内初始化
有的朋友可能会脑子一热,干出了下面这种事
class Q
{
private:
int a=30;
}
在类里初始化了a,但是我们要明白一点定义类不会自动创建对象
然后如果我们创建了一个Q对象,如果我们不去对他初始化的化,它的值就将会是30
我们可以看个例子
#include<iostream>
using namespace std;
class AA
{
private:
int a_=9;
int b_=7;
public:
void print()
{
cout << a_ << endl;
cout << b_ << endl;
}
};
int main()
{
AA w ;
w.print();
}
结果是
9
7
注意这种情况只适用于整型
类里的const
const与函数
1.如果函数的参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
2.如果输入参数以值传递的方式传递对象,则宜改用“const&”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
3.在C++中,传递一个参数时,通常先选择通过引用传递,而且是通过常量(const)引用。
我们看下面这个例子
#include<iostream>
using namespace std;
class AA
{
private:
int a_;
public:
AA(int a)
{
a_ = a;
}
void B()
{cout<<"调用成功"<<endl;}
};
int main()
{
const AA w(2);//注意const
w.B();//这不让你调用
}
把对象声明为const的了就发现不能正常调用它的函数了,那怎么办呢?
解法是C++将const关键字放在函数的括号后面。保证函数不会修改调用对象。
也就是说,我们如果想调用B()函数,就得像下面这么写
#include<iostream>
using namespace std;
class AA
{
private:
int a_;
public:
AA(int a)
{
a_ = a;
}
void B() const //后面加const
{cout<<"调用成功"<<endl;}
};
int main()
{
const AA w(2);//注意const
w.B();
}
因此我们定了一个规矩,只要类方法不修改调用对象,就得在该函数原型和定义后加上const
const修饰数据成员
1.const数据成员只在某个对象生存期内是常量,而对于整个类而言它则是一个变量。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
2.const在每个对象中分配内存。
3.const修饰的数据成员生命周期是对象的生命周期,不同的对象其const数据成员的值可以不同。
4.不能在定义类的构造函数体内初始化const数据成员。const数据成员的初始化只能在类构造函数的初始化表中进行。
看个例子
class Temp
{
public:
Temp(int a, int b);
private:
const int m_a; //此时m_a是一个变量,会分配内存。
int m_b;
};
Temp::Temp(int a, int b) : m_a(a) //再给构造函数分配空间前就已经给m_a赋值了。
{
m_b = b;
}
int main()
{
Temp t(10, 20);
return 0;
}
/*
m_a
在Temp的生命周期里是一个变量,
但是在t的生命周期里是一个常量。
*/
const修饰类成员函数
任何不修改成员数据的函数最好都声明成常成员函数。
用const修饰的函数,在声明和定义时都要加const。
构造函数和析构函数都不能为const(因为他们必然修改对象)。
class Person
{
public:
void display() const;
private:
int m_id;
int m_age;
}
void Person::display() const
{
cout << m_id << m_age << endl;
}
const修饰类对象
const修饰类对象(方式与普通的数据类型定义一样:const Person t);
用const修饰的对象只能调用const成员函数。
例子
#include <iostream>
using namespace std;
class R
{
public:
R(int r1,int r2);
void print();
void print1() const; //常成员函数
private:
int m_r1,m_r2;
const int mc_d1,mc_d2;
};
R::R(int r1,int r2):mc_d1(250),mc_d2(200) //const数据成员初始化只能在类构造函数的初始化列表中进行
{
m_r1 = r1;
m_r2 = r2;
}
//this指针是隐藏在每一个类的成员函数中的特殊指针
void R::print() //此时this指针是: R *this;
{
cout << m_r1 << ":" << m_r2 << endl;
cout << mc_d1 << ":" << mc_d2 << endl;
}
void R::print1()const //常成员函数,此时this指针是:const R *this;
{
cout << m_r1 << ":" << m_r2 << endl;
cout << mc_d1 << ":" <<mc_d2 << endl;
}
int main()
{
R a(5,4);
a.print();
a.print1();
const R b(20,52);
b.print1();
// b.print();
return 0;
}
const的例外
1、要使某个成员变量在const对象中可以被修改、有两种方式:
把常函数中隐含的this指针强制转换为非const。
更为常用的规范做法是:把希望可被改变的数据成员声明为mutable。
2、mutable的用法const 所修饰的函数中,要由编译器负责保护类中的成员变量不被修改。
mutable则是用来修饰类的成员变量,让该变量在const 所修饰的成员函数中可以被修改。
并且const 修饰的函数只能是类的成员函数,mutable修饰的变量只能是类的成员变量。
#include <iostream>
using namespace std;
class Y
{
private:
//int j;
mutable int j; //mutable修饰的,所以可以在const修饰的成员函数中被修改
static int i; //静态成员变量
public:
void f() const;
Y(int x);
void print()const ;
};
int Y::i = 5; //静态成员变量初始化
Y::Y(int x):j(x)
{
}
void Y::print() const
{
cout << "j = " << j << endl;
cout << "i = " << i << endl;
}
void Y::f()const
{
//((Y*)this)-> j = 100; //通过强制类型转换把本来是const Y *this的指针转化成Y *this。
this-> j = 100;
}
int main()
{
const Y y(10);
const Y y1(20);
y.print();
y.f();
y.print();
y1.print();
return 0;
}
类与static
静态类
1.只能包含静态成员(静态方法或静态变量),非静态成员是不能使用的,而非静态类可以包含静态的方法、字段、属性或事件,且无论对这个非静态类创建多少个实例,它的静态成员都只有一个。
2.不能对其实例化。
3.不能被继承,因为静态类本质是一个抽象的密封类。
4.不能包含实例构造函数。
静态变量
1.static只能修饰成员变量,不能修饰局部变量。
2.表示每次重新使用该变量所在方法、类或自定义类时,变量的值为程序这次运行最后一次为变量赋值时的值。
3.静态变量一直记录变量的值,一直到下次赋值时。
4.不同线程下访问的静态属性总是同一属性,如果某一线程更改了属性值,将造成其他线程访问属性值的错误。因此方法中访问同一静态属性就需要使用lock关键字,或创建互斥对象来保持静态属性在同一时间只能被某一对象的属性或方法 访问。
5.静态成员只被创建一次,所以静态成员只有一份,而实例成员有多少个对象,就有多少个成员。
6.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。通常静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但是如果静态成员是const整数类型或者枚举型,则可以在类声明中初始化。
下面举个例子
class AA
{
private:
enum{months=12};//枚举型
static const int a=8;//const整数型
//两者的作用域都为类
}
静态方法
1.在方法(函数)前用static修饰,表示此方法为所在类或所在自定义类所有,而不是这个类的实例所有。
2.在静态方法中只能直接调用同类中其他的静态成员(包括变量和方法), 而不能直接访问类中的非静态成员。
class AA
{
private:
int a_;
public:
static void A()
{
cout << a_ << endl;//这是不可以的
}
};
3.每一个线程在同一时间访问的静态方法都是不同的,因此静态方法在多线程调用中不会产生冲突。
4.在静态方法中不能直接调用实例成员,因为静态方法被调用时,对象还有可能不存在。
5.this/base关键字在静态方法不能使用,因为有可能对象还不存在。
6.静态方法只能被重载,不能被重写,因为静态方法不属于类的实例成员。
静态构造函数
1.静态类可以有静态构造函数,静态构造函数不可继承。
2.可以用于静态类,也可用于非静态类。
3.无访问修饰符、无参数,只有一个static标志。
4.不可被直接调用,当创建类实例或引用任何静态成员之前,静态构造函数被自动执行,并且只执行一次。
什么时候适合用static修饰
1. 当变量需要被共享时可以将变量定义为静态变量。
2. 当方法需要被反复调用时可以将方法定义为静态方法。
3. 当一个类中包含的成员都是静态时可以将类定义为静态类。
使用static时需要注意的地方
静态类中的所有成员也必须是静态。
非静态类中的成员可以是静态也可以是非静态
一个静态的函数内部只能使用该函数外部的静态成员。
一个类中的公开静态成员,在另外一个类中通过直接使用类名点的形式调用这个成员。