C++类C++程序设计允许程序员使用类(class)定义特定程序中的数据类型。这些数据类型的实例被称为对象 ,这些实例可以包含程序员定义的成员变量、常量、成员函数,以及重载的运算符。语法上,類似C中结构体(struct)的扩展,C中结构体不能包含函数以及重载的运算符。 C 结构体与C++ 类的对比在 C++ 中,结构体 是由关键词 聚合类聚合类是一种没有用户定义的构造函数,没有私有(private)和保护(protected)非静态数据成员,没有基类,没有虚函数[2]。这样的类可以由封闭的大括号用逗号分隔开初始化列表[3]。下列的代码在 C 和 C++ 具有相同的语法: struct C
{
int a;
double b;
};
struct D
{
int a;
double b;
C c;
};
// initialize an object of type C with an initializer-list
C c = { 1, 2 };
// D has a sub-aggregate of type C. In such cases initializer-clauses can be nested
D d = { 10, 20, { 1, 2 } };
POD 结构一个POD结构(普通旧式数据结构)是一个不包含非POD结构、非POD联合(或者这些类型的数组)或引用的非静态成员变量(静态成员没有限制),并且没有用户定义的赋值运算符和析构器的聚合类。[1] 一个POD结构可以说是C C结构与C++ POD结构共有的属性
声明和使用C++ 的结构体和类具有他们自己的成员。这些成员包括变量(包括其他结构体和类),被看做方法的函数(特定的标示符或重载的运算符),构造函数以及析构函数。成员被声明成为公共或私有使用说明符 基本声明和成员变量类和结构体的声明使用关键词 下面的代码段实例了结构体和类的声明:
以上两个声明在功能上是等价的。每一段代码都定义了一个类型 在其中一个声明之后(不能同时使用两个), #include <iostream>
#include <string>
using namespace std;
class person
{
public:
string name;
int age;
};
int main ()
{
person a, b;
a.name = "Calvin";
b.name = "Hobbes";
a.age = 30;
b.age = 20;
cout << a.name << ": " << a.age << endl;
cout << b.name << ": " << b.age << endl;
return 0;
}
执行以上代码将会输出 Calvin: 30 Hobbes: 20 成员函数成员函数是C++ 的类和结构体的一个重要特性。这些数据类型可以包含作为其成员的函数。成员函数分为静态成员函数与非静态成员函数。静态成员函数只能访问该数据类型的对象的静态成员。而非静态成员函数能够访问对象的所有成员。在非静态成员函数的函数体内,关键词 class person
{
std::string name;
int age;
public:
person() : age(5) { }
void print() const;
};
void person::print() const
{
cout << name << ";" << this->age << endl;
/* "name"和"age"是成员变量。
"this"关键字的值是被调用对象的地址。其类型为
const person*,原因是该函数被声明为const。
*/
}
在上面的例子中 通过使用成员函数 a.print();
b.print();
上述的 非静态成员函数,可以用const或volatile关键词限定。const限定的成员函数不能修改其他数据成员(除了具有mutable的例外),也不能调用非const限定的其他成员函数。编译实现时,通常是在const限定的成员函数体内,this所指向的数据成员自动具有const限定,因此是只读的。const对象只能调用const成员函数;volatile对象只能调用volatile限定的成员函数。反之,没有受到限定的普通对象可以调用所有的成员函数,不论它是否为cv限定。构造函数、析构函数不能cv限定。 继承非POD类的内存布局没有被C++标准规定。例如,许多流行的C++编译器通过将父类的字段和子类的字段并置来实现单继承,但是这并不被标准所需求。这种布局的选择使得将父类的指针指向子类的操作是平凡的(trivial)。 例如,考虑: class P
{
int x;
};
class C : public P
{
int y;
};
一个 +----+ |P::x| +----+ ↑ p 一个 +----+----+ |P::x|C::y| +----+----+ ↑ p 因此,任何操纵 多重继承并不那么简单。如果一个类 关于多重继承的更多信息,参看虚继承。 重载运算符C++容許程序員重載某些運算符,目的是補充庫中未能提供的針對特定類的運算符。同理,很多時自定類也因為內建庫不能提供指定運算符而需要重載。 另外,當程式設計師沒有重載或定義某些運算符時,編譯器會自動地建立它們,例如三法則中的複製指定運算子( 依照慣例,重載運算符時應模擬運算符本身意義的功能,例如重載運算符「*」時,程序員義務重載為兩數之乘法(或其他,視數學或程式上的意義)。另外,宣告一結構如 struct integer
{
int i;
integer(int j = 0) : i(j) {}
integer operator*(const integer &k) const
{
return integer (i * k.i);
}
};
或 struct integer
{
int i;
integer(int j = 0) : i(j) {}
integer operator*(const integer &k) const;
};
integer integer::operator*(const integer &k) const
{
return integer(i * k.i);
}
在這裡, 而 二元可重载运算符二元運算符會用函數方式並以「 integer a = 1;
/* 這裡的等號是其中一種二元運算符,
我們可利用重載運算符(=)的方式
來提供初始化功能,而左方的變數i
就是類物件本身,右方的數字1則是
傳入參數。 */
integer b = 3;
/* 變數名字跟類物件內的變數無關 */
integer k = a * b;
cout << k.i << endl; //輸出3
以下是二元可重載運算符列表:
運算符( 每個運算符是互相獨立存在,並不依賴其他運算符。例如運算符( 一元可重载运算符一元運算符跟上述的運算符相似,只是一元運算符只會載入類物件本身( 以下是一元可重載運算符列表:
重載一元運算符時有區分前置和後置式,一元前置運算符按以下格式編寫:
而後置運算符按以下格式編寫:
括号重载括號運算符有兩種,分別是方形括號運算符( 方形括號運算符按以下格式重載:
圓形括號運算符按以下格式重載:
注意,參數是指定在第二個括號之中,第一個括號只是運算符符號。 构造函数有时软件工程师会想要他们的变量在声明时有一个默认值。这可以通过声明构造函数做到。 person(string N, int A)
{
name = N;
age = A;
}
成员变量可以像下面的例子一样,利用一个冒号,通过一个初始化序列初始化。这与上面不同,它进行了初始化(使用构造函数),而不是使用赋值运算符。这对类类型来说更有效,因为它只需要直接构造;而赋值时,它们必须先使用默认构造函数进行第一次初始化,然后再赋予一个不同的值。而且一些类型(例如引用和const类型)不能被赋值,因而必须通过初始化序列进行初始化。 person(std::string N, int A) : name(N), age(A) {}
注意花括号不能被省略,即使为里面为空。 默认值可以给予最后的几个参数类帮助初始化默认值。 person(std::string N = "", int A = 0) : name(N), age(A) {}
在上面的例子中,当没有参数给予构造函数时,等价于调用以下的无参构造函数(一个默认构造函数): person() : name(""), age(0) {}
构造函数的声明看起来像一个名字和数据类型相同的函数。事实上,我们的确可以用函数调用的形式调用构造函数。在这种情况下一个 int main()
{
person r = person("Wales", 40);
r.print();
}
以上的例子创建了一个临时的person对象,然后使用复制构造函数将其赋予r。一个更好的创建对象的方式(没有不需要的拷贝): int main()
{
person r ("Wales", 40);
r.print ();
}
具体的程序行为,可以也可以不和变量有关系,可以被作为一部分加入构造函数。 person()
{
std::cout << "Hello!" << endl;
}
通过以上的构造函数,当一个 默认构造函数当类没有定义构造函数时,默认构造函数将被调用。 class A { int b;};
//使用括号创建对象
A *a = new A(); //调用默认构造函数,b会被初始化为'0'
//不使用括号创建对象
A *a = new A; //仅分配内存,不调用默认构造函数,b会有一个未知值
然而如果用户定义了这个类的构造函数,两个声明都会调用用户定义的构造函数。而在用户定义的构造函数中的代码会被执行,并且不会赋予b默认值。 析构函数一个析构函数是一个构造函数的逆,当一个类的一个实例被销毁时会被调用,例如当一个类在块(一组花括号“{}”)中被构造的一个对象会在关闭括号后删除,之后析构函数被自动调用。它会在清空保存变量的内存位置时被调用。析构函数可以在类被销毁时用来释放资源,例如堆分配的内存和打开的文件。 声明一个析构函数的符号类似于构造函数。它没有返回值而且方法的名称和在类的名称前加上波浪线(~)相同。 ~person()
{
cout << "I'm deleting " << name << " with age " << age << endl;
}
另外要注意的是,析構函數是不容許參數傳遞。然而,與構造函數一樣,析構函數可以被顯式調用: int main()
{
person someone("Wales", 40);
someone.~person(); //此時會輸出一次"I'm deleting Wales with age 40"
return 0; //第二次輸出"I'm deleting with age 40"
}
/* 在這裡,程式結束時會自動調用析構函數,
而person.name在第一次調用析構函數時已被清除,
但person.age會按編譯器而定,
沒能在第一次調用析構函數時清零。 */
构造函数与析构函数的相似点
类模板类模板,是对一批仅仅成员数据类型不同的类的抽象,程序员只要为这一批类所组成的整个类家族创建一个类模板,给出一套程序代码,就可以用来生成多种具体的类,这类可以看作是类模板的实例,从而大大提高编程的效率。 属性C++语法试图使一个结构的所有方面看起来像一个基本数据类型。因此,运算符重载允许结构像整数和浮点数一样操作,结构的数组可以通过方括号声明( 内存消耗结构的内存消耗至少是组成变量的内存大小的总和。参考如下 struct twonums
{
int a;
int b;
};
这个结构包含两个整型。在当前许多 C++ 编译器中,整型默认是32 位整型, 所以每个成员变量消耗 4 个字节的内存.因而整个结构至少(或者正好)消耗 8 个字节的内存,见下图。 +----+----+ | a | b | +----+----+ 然而,编译器可能在变量或者结构的结尾添加空的位, 这样可以保证和给定的计算机结构匹配,通常是把变量添加到 32 位。如下例子所示的结构: struct bytes_and_such
{
char c;
char C;
short int s;
int i;
double d;
};
可看成 +-+-+--+--+--+--+--------+ |c|C|XX|s | i | d | +-+-+--+--+--+--+--------+ 在内存中, XX 表示两个未被使用的空位元。 因为结构可能会使用指针和数组去声明 或者初始化变量,结构的内存消耗不一定是固定的。另外一个内存消耗不固定的例子是模板结构。 位字段位字段(Bit field)可以被用来定义比内置类型还要小的类成员变量。通过这个字段定义的变量,只可以像使用内置的整数类型(例如int, char, short, long...)那样子使用。 struct A
{
unsigned a:2; // 可以存储0-3的数字,占据一个int前2 bit的空间.
unsigned b:3; // 可以存储0-7的数字,占据之后3 bit的空间.
unsigned :0; // 移动到下一个内置类型的末尾
unsigned c:2;
unsigned :4; // 在c和d中间加4bit的空白
unsigned d:1;
unsigned e:3;
};
// 内存结构
/* 4 byte int 4 byte int
[1][2][3][4] [5][6][7][8]
[1] [2] [3] [4]
[a][a][b][b][b][][][] [][][][][][][][] [][][][][][][][] [][][][][][][][]
[5] [6] [7] [8]
[c][c][][][][][d][e] [e][e][][][][][][] [][][][][][][][] [][][][][][][][]
*/
位字段不能在结构体中使用,它只能在使用struct或者class关键字定义的类中使用。 按引用传参this关键字complex& operator+=(const complex & c)
{
realPart += c.realPart;
imagPart += c.imagPart;
return *this;
}
参见參考
資料來源
|