C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类被称为类的成员。
类定义
定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据(其中定义的所有数据都是没有被分配存储空间的)但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字class 定义 Box 数据类型,如下所示:
class Box
{
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected,这个我们稍后会进行讲解。
extren用于对象
file1:
class A{
public :
A(int a){};
}
int i=100;
class A a(i);//A a(i);也可以
file2;
extern class A a(int);//指定调用哪个构造函数创建的
extern A a(int);
extern A a;
上面三种都是没有问题的
定义对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:
Box Box1; // 声明 Box1,类型为 Box
class Box Box2; // 声明 Box2,类型为 Box
上面定义了两个Box类型的对象,然后有默认的构造函数来初始化对象。
初始化对象
class A{
public :
A(int a){
std::cout<<"2"<<std::endl;
};
A(){
std::cout<<"1"<<std::endl;
};
};
//隐式调用构构造函数
class A a1;//千万不要加(),不然就会变成返回A的函数了
class A a2(1);
//显示调用构造函数
class A b1=A();//不可以省略括号
class A b2=A(1);
//new
class A *p1=new A;//可以忽略()
ass A *p2=new A(1);
//复制
class A c1;
class A c2;
c1=c2;//调用复制构造函数重新用c2的数据初始化c1
从上面我们可以知道,对于初始化对象我们有四种方法种方法,隐式调用构造函数,显示调用构造函数,new还有复制。
除了上面普通形式 还有一种特殊的格式
A a=1;
当构造函数只有一个参数的时候可以使用一个值初始化对象(后面讲)
Tips:
1、无法调用对象调用构造函数,
2、对于A a=A(1);c++对这样的初始化来说 编译器 有两种实现
第一种实现 创建一个A的对象a,并将成员初始化
第二种实现 创建一个临时变量然后用复制构造函数将临时变量内容复制到a中,并丢弃临时变量。如果使用这种方式,将调用临时变量的构造函数和析构函数(析构函数的调用时间由编译器决定,不一定复制完了之后就调用)
3构造函数的作用是在创建变量之前完成你想要指定的工作
变量 &对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。
我们曾说为了强调面向对象编程特意不把对象叫做变量,然而变量是包括对象的。所以很的变量的概念是可以用在对象上的。
比如变量的中提到的存储区,连接性、作用域、生命周期和对象是完全一样的
访问数据成员
类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问。为了更好地理解这些概念,让我们尝试一下下面的实例:
#include <iostream>
using namespace std;
class A {
public:
void f( ) {};
int i;
};
int main( )
{
A a1;
A *a2 = new A;
a1.f();
a2->i;
return 0;
}
如上所示对于指针我们要用->对于非指针我们用.
类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
让我们看看之前定义的类 Box,现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员:
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);// 返回体积
};
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以您可以按照如下方式定义Volume() 函数:
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
您也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:
double Box::getVolume(void)
{
return length * breadth * height;
}
我们可以再类中声明并定义函数
class {
public :
void f(){};
};;
这样函数就变成类内联函数了。
类访问修饰符
数据隐藏是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问说明符。
一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。
class Base {
public:
// public members go here
protected:
// protected members go here
private:
// private members go here
};
公有(public)成员
公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的
#include <iostream>
using namespace std;
class Line
{
public:
double length;
void setLength( double len );
double getLength( void );
};
// 成员函数定义
double Line::getLength(void)
{
return length ;
}
void Line::setLength( double len )
{
length = len;
}```
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
// 不使用成员函数设置长度
line.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of line : " << line.length <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Length of Length of line : 10
私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。例如在下面的类中,width 是一个私有成员,这意味着,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员:
class Box
{
double width;
public:
double length;
void setWidth( double wid );
double getWidth( void );
};
实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,如下所示:
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Length of box : 10
Width of box : 10
保护(protected)成员
保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
在继承小节将学习到派生类和继承的知识。现在您可以看到下面的实例中,我们从父类 Box 派生了一个子类smallBox。
下面的实例与前面的实例类似,在这里 width 成员可被派生类 smallBox 的任何成员函数访问。
#include <iostream>
using namespace std;
class Box
{
protected:
double width;
};
class SmallBox:Box // SmallBox 是派生类
{
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
return width ;
}
void SmallBox::setSmallWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
SmallBox box;
// 使用成员函数设置宽度
box.setSmallWidth(5.0);
cout << "Width of box : "<< box.getSmallWidth() << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Width of box : 5
类构造函数 & 析构函数
类的构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行,但是你不可以通过对象调用它。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
下面的实例有助于更好地理解构造函数的概念:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Object is being created
Length of line : 6
带参数的构造函数
默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,如下面的例子所示:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line(10.0);
// 获取默认设置的长度
cout << "Length of line : " << line.getLength() <<endl;
// 再次设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Object is being created, length = 10
Length of line : 10
Length of line : 6
默认构造函数
A a;//A //千万不要A a(); 这表示返回A类型的函数
这样可以初始化对象为什么呢?如果像上面那样没有显示声明用什么构造函数来初始化对象的话c++就会在创建对象时会调用默认构造函数来初始化对象 ,而如果我们没有定义默认构造函数时编译器会创建默认的构造函数函数A::A();然而在这种事当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。现在我们来说定义默认构造函数;
有两种方式创建默认构造函数
1、自己定义默认构造函数
A::A();
现在来看看下面的例子
class A{
private :
//...
public:
//...
}
A a=A(1); //显示调用构造函数
A a();//返回A类的一个函数
A a;//调用默认构造函数
2、提供默认参数的默认构造函数
A::A(int a=0);
使用这种方式的时候注意定义类函数默认参数后。定义函数时就不要在定义一般默认参数了,不然会出现 重设 默认认参数错误
class A{
public :
A(int a=0);
};
A::A(int a){//不能再写int a=0了
std::cout<<"2"<<std::endl;
};
class A a1;
类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
下面的实例有助于更好地理解析构函数的概念:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Object is being created
Length of line : 6
Object is being deleted
Note:
1析构函数没有返回值也不能写参数
2不应该显示调用析构函数,而又编译器决定
3析构函数里面应该写一些对象消亡之后的处理工作,比如释放内存之类的
类中的自动转换和强制转换
使用构造函数的自动转换
参考类型转换
类的拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
• 通过使用另一个同类型的对象来初始化新创建的对象。
• 复制对象把它作为参数传递给函数。
• 复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须自己定义一个拷贝构造函数,不然再复制的时候就变成浅拷贝(只拷贝指针地址)了。拷贝构造函数的最常见形式如下:
classname (const classname &obj) {
// 构造函数的主体
}
在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的。
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "Normal constructor allocating ptr" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "Copy constructor allocating ptr." << endl;
ptr = new int;
*ptr = *obj.ptr; // copy the value
}
Line::~Line(void)
{
cout << "Freeing memory!" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "Length of line : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line(10);
display(line);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Normal constructor allocating ptr
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!
下面的实例对上面的实例稍作修改,通过使用已有的同类型的对象来初始化新创建的对象:
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "Normal constructor allocating ptr" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "Copy constructor allocating ptr." << endl;
ptr = new int;
*ptr = *obj.ptr; // copy the value
}
Line::~Line(void)
{
cout << "Freeing memory!" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "Length of line : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数,也可以使用Line line2(line1)
display(line1);
display(line2);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Normal constructor allocating ptr
Copy constructor allocating ptr.
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!
Freeing memory!
Note:
c++除了会默认生成构造函数,析构函数,复制构造函数。还会生成=运算符重载函数。和复制构造函数有一点像,复制构造函数会用一对象生成另一个对象时调用,而=运算符重载函数会在对象与对象赋值时调用,而且,默认的=运算符重载函数进行的也是浅拷贝
const函数
class A{
private:
int var;
public:
void f(int)const;
}
想上面在函数后面声明后面加上const之后。表示这个函数就不会修改类中的成员了。例如下面就会被视为错误
void A::f(int i)const{var=i};
Note:
非const的对象是不能调用const函数的。
但是const对象和非const对象都可以调用非const函数
this 指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
下面的实例有助于更好地理解 this 指针的概念:
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2))
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1#
指向类的指针#
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
下面的实例有助于更好地理解指向类的指针的概念:
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
Box *ptrBox; // Declare pointer to a class.
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
// 保存第二个对象的地址
ptrBox = &Box2;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box2: " << ptrBox->Volume() << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Constructor called.
Constructor called.
Volume of Box1: 5.94
Volume of Box2: 102
类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员放置在类初始化,但是可以在类的外部通过使用范围解析运算符:: 来重新声明静态变量从而对它进行初始化,同时不加上static,如下示例所示。
下面的实例有助于更好地理解静态数据成员的概念:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;//当定义静态成员数据的时候不使用static
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Constructor called.
Constructor called.
Total objects: 2
Tips:
不能在类中初始化静态变量是因为如果初始化了静态变量,那么创建别的变量的时候,会修改静态变量的值,比如类中的静态变量初始化值为1,创建一个对象a1时,这个静态变量被初始化为1,我们开始让这个静态变量+1,现在这个静态变量为2,这时候创建第二个变量a2,于是静态变量又被初始化为1了
作为特例,基本类型的const 静态变量都是可以通过常量在类中初始化的,因为基本类型的const常量是不可以修改的。
class A{
public:
static const int a=16;
static const std::string s;
};
const A:: a=19;//error,const时不能重新修改的
const std::string A::s="123";
静态函数成员
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符:: 就可以访问。在类外面定义静态函数时不加static
静态成员函数只能访问静态数据成员,不能访问其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
下面的实例有助于更好地理解静态函数成员的概念:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2
Note:
静态成员函数只能访问类中的静态成员。我们可以把静态成员想象成是类的函数,而不是对象的函数。
作用域在类中的常量
先来看一个示例
class A{
public:
int in_1=3;//#1[Warning] non-static data member initializers only available with -std=c++11 or -std=gnu++
const int in_2=3;//#2[Warning] non-static data member initializers only available with -std=c++11 or -std=gnu++
static int in_3=3;//#3[Error] ISO C++ forbids in-class initialization of non-const static member 'A::in_3'
static const int in_4=3;//#4
std::string in_5="3";//#5[Warning] non-static data member initializers only available with -std=c++11 or -std=gnu++
int array_6[3];////#6 ok
int array_2[in_1];//[Error] invalid use of non-static data member 'A::in_1'
int array_3[in_2];//[Error] invalid use of non-static data member 'A::in_2'
int array_4[in_3];//因为int_3是error
int array_5[in_4];//ok
}
类是对象的“设计图“。类中定义的数据是没有存储空间的(除了static修饰的,因为static的持续性是整个程序生命周期),类只是告诉编译器在创建对象时按照什么样子开辟内存空间。对类中的数据赋值是错误的。就像上面所示那样。但是为什么有的是警告警告而不是报错呢,在支持c++11或者gun++标准的编译器会在创建完变量后按照上面的定义去初始化。所以array_2和array_3的错误都是in_1和in_2 在编译时不知道多大而报错。为什么#4没有错误且数组array_5使用时没有问题的呢?因为在非类的情况下我们使用const的时候是必须赋值的。不然会报错(变量一开始都有一个初值,无论是原来内存中的,还是系统赋予的,而const修饰的变量赋值一次之后就不允许修改,所以编译器就报错提醒你了赋值,以免系统赋值之后你改不了)所以我们的编译器把const的常量全部替换成了3,但是在类中就不一样了。还记的上面的警告吗。除了静态变量其他变量都是在创建变量之后赋值的。而对于const 的静态变量,编译器知道它只有一个副本而且还永远不会被更改,所以编译器也就用字面量等效替换了,而在类中把一个静态常量的地址赋值给一个静态指针常量指针会报错了也间接说明了这点。
现在我们来看看怎么让上面数组通过编译把
1
class A{
static const int SIZE=3;
int array[SIZE];
//...
}
只有基本数据类型才能这样做,但是c++11标准取消了这个限制
2
class A{
public:
enum {AA=3};
int a[AA];
};
这种方式创建的枚举作用域为这个类,但是这种方式并不会创建类数据成员,也就是说所有对象都不包含枚举,另外SIZE只是一个符号名称,在作用域为整个类的代码中遇到时编译器会用3代替(因为枚举没有名称)
内枚举
enum A{SIZE HEIGHT};
enum B{SIZE WIDTH};
上面传统的枚举会出错,因为SIZE重复了,但是c++11提供了一种新枚举为内的枚举.
enum class A{SIZE};//class还是struct都可以
enum struct B{SIZE};
这样就可以在使用SIZE之前加上 限定词了比如
A a=A::SIZE;
对于传统枚举是根据底层整形类型表示的,
但是c++11统一规定内枚举为int,但是c++11提供了一种语法来完成不同的训责
enum clss:short A{SIZE}
对象数组
假设有一个类A他有很多不同的构造函数,当创建一个该对象的数组时默认调用默认构造函数来初始化,不过我们可以主动调用够着函数来初始化对象,示例如下:
class A{
public :
A(){
cout<<"default constructor is called"<<endl;
}
A(int){
cout<<"int constructor is called"<<endl;
};
};
const int SIZE=3;
class A ar[SIZE]={
A(1),
A(2),
};
输出结果:
int constructor is called
int constructor is called
default consructor is called
Tips:
因为如果你提供了构造函数之后,编译器是不会提供默认的构造函数的。所以对于上面的例子来说,如果你不提供默认构造第三个元素会初始化失败的
嵌套类
c++允许你在一个类中声明另一个类
class Out{
int val_out;
public :
class In{
public:
int val_in;
In();
}
}
仅当被嵌套类(In)声明在public下才能在包含类(Out)外面访问被嵌套类
要想定义被嵌套类中函数就需要使用两次域解析符
Out::In::In(){}
In类可以访问Out类的private成员,但是Out类无法访问In类的private成员,但是我们可以把In类定义为Out类的public成员,然后把In类所有成员声明为public。
class Out{
int val_out;
class In{
public:
int val_in;
In();
}
public :
}
这样在不违背”数据私有“的前提下访问In类的数据成员(因为对于外部In类是不可见的)
友元
我们知道c++中公用方法是访问类中private成员的唯一方法,但是有时候未免太严格了,所以出现了friend(友元)
友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;//还是需要变量名加.来访问,而不是像类中的函数那样直接访问
}
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Width of box : 10
友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
class A
{
// …
public:
friend class B;
// …
};
经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员
Note:
- 友元关系不能被继承。
- 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
- 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明