一、类和对象的定义
1.类的语法形式
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护型成员
};
公有成员:类与外部的接口,任何外部函数都可以访问类内的公有数据类型和函数
私有成员:只允许本类的对象里的成员访问
保护成员:在继承和派生上与私有成员有轻微差别
class clock
{
int hour,minute,second;
public:
void setTime(int newH=0,int newM=0,int newS=0);
//可以这样用,如果没有提供实参,函数将使用初始化的值作为参数的值
void showTime();
};
可以在函数里定义参数的初始值,这里类声明后面要加分号,否则,编译器会报错
2.类的成员函数
在类中声明函数原型
在类外函数应写成 类名::函数名(参数)的形式
void Clock::showTime()
{
;
}
可以在类中给出函数体,形成内联成员函数
class clock
{
int hour,minute,second;
public:
void setTime(int newH=0,int newM=0,int newS=0);
void showTime()
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
};
内联成员函数:为了提高运行时的效率,对于简单的函数可以声明为内联形式(即不能有循环和switch语句),如果想在类的声明外定义成员函数,并使之成为内联函数,可以在定义函数时候加inline关键字 如 inline void Fun::fun(){.......},
函数也是通过栈来实现的,如果不使用内联函数,程序在执行的时候函数还有个入栈出栈,以及给参数赋值等操作,这就花费了大量时间,举个例子
int fun(int x)
{
return x*2;
}
int main()
{
int a,b;
a=2;
b=fun(a);
}
这个fun函数如果是内联函数的话,编译器就会把程序编译成这样
int main()
{
int a,b;
a=2;
b=a+a;
}
虽然在编译的环节增加了时间耗费,但是在运行的时候就减少了时间耗费。
函数前面加上了inline之后,就是函数的声明了,也就是说只需要在.h文件里声明函数就行了,在.cpp文件里不需要加函数的body了。
inline和c的define的含义差不多,但是使用inline的好处就是 inline函数是可以进行类型检查的
比如
//define
#define f(a) (a)+(a)
int main()
{
double a=4;
printf("%d",f(a));
}
//inline
inline int f(int i)
{
return i*2;
}
int main()
{
double a=4;
printf("%d",f(a));
}
inline里面由于f返回类型是double 编译器会提示你%d和double不匹配,但是define 定义的函数,就忽略了返回值类型,编译器不会报错。
二、类的构造函数
构造函数:类中用于初始化的函数,在对象被创建时就会被执行
构造函数的形式:
1.函数名与类名相同
2.不能定义返回值类型,也不能有return语句
3.可以有形式参数,也可以没有形式参数
4.可以是内联函数
5.可以重载
6.可以带默认参数值
默认构造函数:
1.参数表为空的构造函数
2.全部参数都有默认值的构造函数
如果声明类的时候没有声明构造函数,计算机会自动的运行一个生成的构造函数。
构造函数的实现👇
#include<bits/stdc++.h>
using namespace std;
class Clock
{
int hour,minute,second;
public:
void setTime(int newH=0,int newM=0,int newS=0);
void showTime();
Clock(int newH,int newM,int newS);
};
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS)
{
;
}
int main()
{
Clock c(0,0,0);
return 0;
这个构造函数之后冒号之后的内容是初始化列表,意思是把newH赋值给hour……
我们也可以在上面的类中在加入一个默认构造函数,在创建对象的时候,程序会自动识别使用那个有参数的构造函数还是那个默认的构造函数
当程序提供了非默认的带参数的构造函数之后如fun::fun(int x)之后,我们在主函数里再这样来定义对象(fun a;是错误的)。解决的方法有两个
1.给予非默认的构造函数里的参数初始值
fun::fun(int x=0);
2.利用函数重载来定义另一个构造函数
fun::fun();
带参数的构造函数也可以是默认构造函数,只要参数给了初始值
A::A(int n=0):x(n){}
A::A(){}
默认构造函数只能有一个,所以这样是错的。
因为如果有 A b;编译器不知道该调用哪一个构造函数了,这就犯了二义性问题。
对象数组
初始化方式 是 类名 对象名[对象数量] ={构造函数(参数),构造函数(参数)....};
如果构造函数的参数只有一个可以像数组一样初始化, 类名 对象名[对象数量] ={参数1,参数2,参数3....};
委托构造函数:
当我们在类中重载多个构造函数的时候,我们往往发现,这些构造函数只是形参表不同,初始化列表不同,而其他都是一样的,这时候我们并不一定需要在创建多个构造函数,只需要使用委托构造函数就行
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS)
{
;
}
Clock::Clock()://默认构造函数
hour(0),minute(0),second(0)
{
;
}
两个构造函数只是参数不同,所以我们可以这样用
Clock::Clock(int newH,int newM,int newS):
hour(newH),minute(newM),second(newS)
{
;
}
Clock::Clock():Clock(0,0,0)
{
}
※复制构造函数
#include<iostream>
using namespace std;
class A
{
public:
int i;
int* p;
A(int x=0):i(x){}
};
int main()
{
A a(1);
A b;
b=2;
cout<<b.i<<endl;
b=a;
return 0;
}
上面代码输出结果是 2,
这说明b=2这样的语法是行得通的,当我们把2换成一个对象的时候,即b=a的时候相当于用a来初始化b
也就是说,相当于调用了一个构造函数(这个构造函数是这样的 A::A(const A &a){this->i=a.i}),这个函数就是复制构造函数,平时我们不需要去管他,因为程序会自动生成一个默认的复制构造函数,但是,当我们的类中还有指针的成员的时候,如果使用默认的复制构造函数,就会只是把指针的地址复制,但是两个指针指向的都是同一块内存,在析构的时候,就容易发生错误。
移动构造函数
想一想,如果我们在函数里要返回一个带有指针的对象,我们该怎样做呢?
因为函数里面的对象在离开作用域之后就被析构了,所以如果我们直接使用默认的复制构造函数就会发生错误,
所以解决的方法一就是使用复制构造函数
#include <iostream>
using namespace std;
class A
{
private:
int *a;
public:
A(int aa=0):a(new int(aa)) {}//构造函数
A(A &p)//复制构造函数
{
a= new int(*p.a);
}
int geta()
{
return *a;
}
~A()//析构函数
{
delete a;
}
};
A fun()
{
A b(1);
return b;
}
int main()
{
cout <<fun().geta()<< endl;
return 0;
}
在fun函数里面,定义了一个对象b,返回b的时候由于b要离开其作用域也就是要被释放了,系统会创建一个临时无名对象,把b的值赋给它,这里要用的复制构造函数,因为对象b里面有指针。
方法二是使用复制构造函数
移动构造函数适用于此种情况
我们把函数里面的对象返回时候,让临时无名对象()的指针等于本该被释放的对象的指针,并将本该释放的对象的指针指向NULL,于是在fun函数里面的那个对象的析构函数执行时候,回detele一个空指针,不会发生任何事情,在主函数里用完了临时无名对象后相干的内存在被释放,这样就只需要释放一次内存空间,提高了效率。
#include <iostream>
using namespace std;
class A
{
private:
int *a;
public:
A(int aa=0):a(new int(aa)) {}
A(A &&p)
{
a= p.a;
p.a=NULL;
}
int geta()
{
return *a;
}
~A()
{
delete a;
}
};
A fun()
{
A b(1);
return b;
}
int main()
{
cout <<fun().geta()<< endl;
return 0;
}
&&表示的是右值引用,c++里的引用不允许引用右值,右值也就是临时的变量。上面代码可能在低版本的编译器上,编译不通过
析构函数:
对象在离开其作用域时候,执行析构函数,用于销毁对象。
与构造函数类似,如果你不定义析构函数,程序会生成一个空的析构函数。
析构函数的表示方法:
~类名()
没有返回类型
一个类的成员可以是另一个类的对象
#include<bits/stdc++.h>
using namespace std;
class A
{
private:
int a,b,c;
public:
A(int newA,int newB,int newC);
A();
};
class B
{
A p1;
A p2;
public:
B(A newp1,A newp2);
};
A::A(int newA,int newB,int newC):a(newA),b(newB),c(newC)
{
}
B::B(A newp1,A newp2):p1(newp1),p2(newp2)
{
}
int main()
{
A fun(1,2,3);
A happy(4,5,6);
B world(fun,happy);
return 0;
}
类的静态成员:
静态成员类似于静态变量,静态变量在整个程序结束前始终保持存在,如下代码中
#include <iostream>
using namespace std;
void show()
{
static int a=0;
a++;
cout<<a<<endl;
}
int main()
{
show();
show();
show();
return 0;
}
输出结果
在函数show中 定义了一个静态整型变量,第一次调用show函数的时候,a=0,结束程序后,a仍然存在,并且a的值不变,相当于全局变量,第二次调用show函数的时候,由于a已经存在了,static int a=0这条语句将不再被执行。
值得注意的是 在类里面的static int a只是一个声明,需要在其他地方定义这个变量,例如下面的静态成员变量 a。
而且,静态成员变量不能通过初始化列表初始化。
#include<bits/stdc++.h>
using namespace std;
class A
{
private:
int x;
static int a;
public:
static void showw(){cout<<x<<endl;}//静态成员函数是无法调用非静态成员变量的
是因为静态成员函数没有this指针,只有这样才能保证静态成员变量可以通过A::a来访问。
void show(){cout<<a<<endl;}
};
int A::a=0;//初始化静态成员变量a,这里不加 static,是因为加了static就说明这个变量只存在于当前这个.cpp文件里,而这与类的相关特点相违背,所以不加static。
int main()
{
A b;
b.show();
//cout<<b.a<<endl;
//cout<<A::a<<endl;//如果a是公有成员,这两种方法都可以访问静态成员变量a
return 0;
}
可以使用类名::成员名来访问静态成员变量或成员函数