cpp基础复习


保研后在做毕设的同时,觉得还是需要巩固一下基础,就把大一的时候的C++课件又过了一遍,大概整理了一点基础的知识, 排版比较混乱,意思意思就行了。

构造函数(Constructors

构造函数是一种用于初始化新创建的对象的方法。它保证已正确初始化之后,才使用对象。

类构造函数的目的是初始化一个类对象的私有数据成员的成员函数。

构造函数的名称始终是类的名称,构造函数没有返回类型。

一个类可以有若干具有不同参数列表的构造函数。不带参数的构造函数是缺省构造函数。

构造函数变为private:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;

class A {
int x = 0;
A() {
x = 100;
}
public:
void test() {
cout << x << endl;
}
};

int main()
{
A* temp = new A();
temp->test();
}

报错如下:

1
“A::A”: 无法访问 private 成员(在“A”类中声明)

而且private构造函数使得该类也无法被继承。

如果是protected的话,可以继承,但是也无法直接创建对象。

析构函数(Destructor)

如果把析构函数定义成private会在delete的报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

using namespace std;

class A {
int x = 0;
~A() {
cout << "end" << endl;
}
public:
A() {
x = 100;
}
public:
void test() {
cout << x << endl;
}
};

int main()
{
A* temp = new A();
temp->test();
delete temp;
}
1
“A::~A”: 无法访问 private 成员(在“A”类中声明)

因为delete会调用析构函数。

const关键字

​ 声明常量数据成员的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Demo {
public:

Demo( ): data1(0) //常量数据成员只能在构造函数初始化列表中初始化
{
// data1 = 0; // 此处不能对常量数据成员data1赋值

data = 0;
}

private:
int data; // 一般的数据成员
const int data1; // 常量数据成员
};

在对象常量里面,这个时候不能调用修改了成员变量的方法。

类的静态成员

  • 静态(static)成员是类的组成部分但不是任何对象的组成部分

  • 通过在成员声明前加上保留字static将成员设为static(在数据成员的类型前加保留字static声明静态数据成员;在成员函数的返回类型前加保留字static声明静态成员函数)

  • static成员遵循正常的公有/私有访问规则。 C++程序中,如果访问控制允许的话,可在类作用域外直接(不通过对象)访问静态成员(需加上类名和::)

  • 静态数据成员具有静态生存期,是类的所有对象共享的存储空间,是整个类的所有对象的属性,而不是某个对象的属性。

  • 与非静态数据成员不同,静态数据成员不是通过构造函数进行初始化,而是必须在类定义体的外部再定义一次,且恰好一次,通常是在类的实现文件中再声明一次,而且此时不能再用static修饰。

  • 静态成员函数不属于任何对象

  • 静态成员函数没有this指针

  • 静态成员函数不能直接访问类的非静态数据成员,只能直接访问类的静态数据成员

静态成员变量在声明后需要在类外进行定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

using namespace std;

class A {
static int count;
public:
static int get_count() {
return count;
}
};
int A::count;

int main()
{
cout << A::get_count();
}

why?

对于class的static data member,其实只是声明了一个scope(还记得class::static_data_member中的**::**么?),既然是声明而已,所以还需要一个定义,之所以需要在类的外面,因为本质来说它和global和static变量没什么区别,都是在数据段的,只是scope不一样,属于class而已。

这里反映出了C/C++里面一些稍微偏底层的复杂的细微的概念,比如scope,storage,life time。
::是指scope,是在class里面声明的,static指storage,是和global一样,在外面定义的。

拷贝构造函数和赋值

通过一个已有的对象初始化一个新建对象

  • 形参类型为该类类型本身且参数传递方式为按引用传递。
  • 用一个已存在的该类对象初始化新创建的对象。
  • 每个类必须有拷贝构造函数:
    • 用户可自定义
    • 未自定义可以缺省,默认采用逐位复制方式利用已存在的对象来初始化新创建的对象。
  • 用类类型本身做形式参数
  • 引用传递是避免在函数调用过程中生成形参副本。(按值传递:实参向形参传递又产生拷贝
  • 该形参一般声明为const,以确保在拷贝构造函数中不修改实参的值
1
C::C(const  C& obj);

拷贝构造函数一般采用浅复制,这意味着可能会出现很多的问题。而且以对象作为函数返回值的时候,会出现如下问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <string.h>
using namespace std;


class NAME {
public:
NAME();
~NAME();
void show();
void set(char* s);
private:
char* str;
};

NAME::NAME() //Name.cpp
{
str = NULL;
cout << "Constructing.\n";
}
NAME:: ~NAME()
{
cout << "Destructing.\n";
if (str != NULL)
delete[]str;
}

void NAME::show()
{
cout << str << "\n";
}

void NAME::set(char* s)
{
if (str != NULL)
delete[]str;

str = new char[strlen(s) + 1];

if (str != NULL)
strcpy(str, s);
}


NAME get_name()
{
NAME obj;
char temp_str[250];
cout << "Input your name: ";
cin >> temp_str;
obj.set(temp_str);
return obj;
}

int main()
{
NAME myname;
myname = get_name();
myname.show();
}

由于以对象的形式返回,意味着会创建作为返回值的临时对象后,撤销原对象,而由于采用了浅复制,所以意味着会直接使用缺省的赋值运算符,进行指针的复制,在撤销原对象的时候,指针指向的内存被释放,但是返回的对象的指针仍然指向那片被释放的内存,所以运行以上代码很可能无法达到正确结果(也可能某些编译器做了优化,但是我使用的gcc 8.2确实无法输出结果)(然后用Visual Studio好像又是这么搞的)

对此我们应该实现deep copy的赋值运算符重载来解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
NAME::NAME(NAME& other)
{
cout << "Copy Constructing.\n";
if (other.str == NULL)
{ str=NULL;
return;
}
str=new char[strlen(other.str)+1];
if (str!=NULL)
strcpy(str, other.str);
}

NAME& NAME:: operator=(const NAME& other)
{
if (other.str == NULL)
{ str=NULL;
return *this;
}

if (str!=NULL) delete []str;

str=new char[strlen(other.str)+1];
if (str!=NULL) strcpy(str, other.str);

return *this; /*若函数的返回值类型为NAME,则此语句会引起
对拷贝构造函数的调用;若函数的返回值类型为
NAME&,则不调用拷贝构造函数*/
}

这个地方把我弄得很晕,因为我也不知道到底是拷贝构造函数起作用还是赋值运算符重载起作用,所以我就做了实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <string.h>
using namespace std;


class NAME {
public:
NAME();
~NAME();
void show();
void set(char* s);
NAME(NAME& other);
NAME& operator=(const NAME& other);
char* str;
};

NAME::NAME() //Name.cpp
{
str = NULL;
cout << "Constructing.\n";
}
NAME:: ~NAME()
{
cout << "Destructing.\n";
if (str != NULL)
delete[]str;
}

void NAME::show()
{
cout << str << "\n";
}

void NAME::set(char* s)
{
if (str != NULL)
delete[]str;

str = new char[strlen(s) + 1];

if (str != NULL)
strcpy(str, s);
}

NAME::NAME(NAME& other)
{
cout << "Copy Constructing.\n";
if (other.str == NULL)
{ str=NULL;
return;
}
str=new char[strlen(other.str)+1];
if (str!=NULL)
strcpy(str, other.str);
}
NAME get_name()
{
NAME obj;
char temp_str[250];
cout << "Input your name: ";
cin >> temp_str;
obj.set(temp_str);
return obj;
}

NAME& NAME:: operator=(const NAME& other)
{
cout << "This is assignment" << endl;
if (other.str == NULL)
{ str=NULL;
return *this;
}

if (str!=NULL) delete []str;

str=new char[strlen(other.str)+1];
if (str!=NULL) strcpy(str, other.str);
return *this; /*若函数的返回值类型为NAME,则此语句会引起
对拷贝构造函数的调用;若函数的返回值类型为
NAME&,则不调用拷贝构造函数*/
}


int main()
{
NAME myname;
cout << "First, return from fun" << endl;
myname = get_name();
cout << "Second, assign from other" << endl;
NAME test = myname;
cout << "last, copy from other" << endl;
NAME test1(myname);
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Constructing.
First, return from fun
Constructing.
Input your name: zhb
This is assignment
Destructing.
Second, assign from other
Copy Constructing.
last, copy from other
Copy Constructing.
Destructing.
Destructing.
Destructing.

经过反复实验,发现,如果是

1
2
3
4
NAME myname;
myname = get_name();// 使用赋值构造函数
NAME test;
test = myname;// 使用赋值重载

如果是

1
NAME myname = get_name();

在gcc下它使用了浅拷贝(疑惑),然后用Visual Studio的话他调用了拷贝构造函数。

总而言之,这个东西太玄学了, 感觉跟编译器关系比较大,建议写代码的时候赋值重载和拷贝构造函数都要实现。

组合

运算符重载

类成员运算符重载

下标运算符重载:

形参为整型

返回值类型为引用类型

类成员运算符重载必须满足:第一操作数(二元运算符的左操作数)必须是本类对象

在这里举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Operator.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

using namespace std;

class INTEGER {
public:
INTEGER(int i = 0) // 构造函数
{
value = i;
}

INTEGER(const INTEGER& other) // 拷贝构造函数
{
value = other.value;
}
private:
int value; // 私有数据
};
int main()
{
INTEGER x(10); // 定义整数对象,用构造函数来初始化
INTEGER y = x; // 定义整数对象,用拷贝构造函数来初始化
INTEGER z; // 定义整数对象,用构造函数的缺省参数初始化
y = x + 2; // 合法调用,用2初始化对象后加入运算
z = 30 + y; // 不合法调用,因为30不是对象
return 0;
}

友元运算符重载

友元:在类的声明中,用friend声明的函数或类,即是该类的友元。

一个类的友元可以是:

  • 游离函数
  • 另外一个类
  • 其他类的成员函数

游离函数做友元:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 类VALUE中定义了一个友元函数set(),注意set()不是该类的成员函数
class VALUE
{
public:
//声明set()为VALUE的友元
friend void set(VALUE obj, int x);
private:
int value;
};

void set(VALUE obj, int x) // 实现友元函数set()
{
obj.value = x; // set()可以象VALUE成员函数一样访问obj的私有和受保护成员
}

另外一个类做友元:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Y;	   // Y类的引用性声明
class X {
public:
// 把Y类声明为X类的友元,则Y类的所有成员函数都是X的友元
friend Y;

private:
int k ;
void m_Xfunc( );
};

class Y {
public:
void m_Yfunc( X& obj );
};

void Y::m_Yfunc( X& obj )
{
obj.k = 100 ; // Y类的成员函数是X的友元,可以访问X的私有和受保护成员
}

把其他类的成员函数作为友元:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Y {
public:
void Yfunc( );
};

class X {
public:
friend void Y::Yfunc(); // 把Y类的Yfunc函数声明为X类的友元
private:
int k ;
void m_Xfunc();
};

void Y::Yfunc( )
{
X obj;
obj.k = 100 ; // 该函数是X的友元,可以访问X的私有和受保护成员
}
  • 解决类成员函数运算符重载存在的问题:第一操作数(二元运算的左操作数)必须是本类对象。

  • 形参设置规则:

  • 一元运算符必须显式声明一个形参。

  • 二元运算符必须显式声明二个形参。

  • 下列运算符不能作为友元重载:

  • = ( ) [ ] ->

  • 友元函数不是该类的成员,因此在友元函数中不能使用this指针。

上面的INTEGER通过修改,改成友元即可以解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;

class INTEGER {
public:
INTEGER(int i = 0) // 构造函数
{
value = i;
}

INTEGER(const INTEGER& other) // 拷贝构造函数
{
value = other.value;
}

//INTEGER operator +(INTEGER other) // 重载加法运算符

//{
// INTEGER temp;
// temp.value = value + other.value;
// return temp;
//}
friend INTEGER operator +(INTEGER a, INTEGER b);
private:
int value; // 私有数据
};

INTEGER operator +(INTEGER a, INTEGER b) {
INTEGER temp;
temp.value = a.value + b.value;
return temp;
}

int main()
{
INTEGER x(10); // 定义整数对象,用构造函数来初始化
INTEGER y = x; // 定义整数对象,用拷贝构造函数来初始化
INTEGER z; // 定义整数对象,用构造函数的缺省参数初始化
y = x + 2; // 合法调用
z = 30 + y;
return 0;
}

对ifstream和ofstream的重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <string>
#include <iostream>
using namespace std;
class Fruit
{
public:
Fruit()
{
name = "apple";
color = "green";
}

friend ostream& operator << (ostream& out, const Fruit& x)
{
out << "name: " << x.name
<< " color: " << x.color << endl;

return out;
}
friend istream& operator >> (istream& in, Fruit& x)
{
cout << "Please enter the name: " << endl;
in >> x.name;

cout << "Please enter the color: " << endl;
in >> x.color;

return in;
}
private:
string name, color;
};
int main()
{
Fruit fruit1;

cout << fruit1;
cin >> fruit1;
cout << fruit1;

cout << "Finished!" << endl;

}

对++和–的重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class COMPLEX {	                                          //complex.h
public:
COMPLEX(double r = 0, double i = 0); // 构造函数
COMPLEX(const COMPLEX& other); // 拷贝构造函数
void print(); // 打印复数

COMPLEX & operator++(); //重载前置++
COMPLEX operator++(int); //重载后置++
COMPLEX & operator--(); //重载前置--
COMPLEX operator--(int); //重载后置--

protected:
double real, image; // 复数的实部与虚部
};
COMPLEX& COMPLEX::operator++() //COMPLEX.CPP
{
real += 1;
image += 1;
return *this;
}

COMPLEX COMPLEX::operator++(int)
{
COMPLEX before = *this;
real += 1;
image += 1;

return before;
}

在这里面后置++的参数没有任何意义,只是为了区分前置++和后置++的。因为

前置++返回的是运行结果,直接返回结果就行了,

后置++返回的是运行前的结果,但是对象本身被修改了,所以得存一下before再返回。

对()重载