C++学习笔记
现在主流的编译型语言包括C、C++、Go、Rust等,它们的编译过程中需要将代码转换成机器语言,因此可以获得更高的执行效率和更好的性能。
而主流的解释型语言包括Python、Ruby、JavaScript等,这些语言需要解释器将代码转换成机器语言并运行,因此相对于编译型语言,它们的执行效率和性能可能会稍低,但是它们通常具有更高的开发效率和更强的灵活性,因为它们可以在运行时动态修改代码。
另外,还有一些语言是即时编译型语言(JIT),例如Java、C#和LuaJIT等,这些语言的编译器会在运行时将代码编译成机器语言,因此它们的执行效率和性能通常比解释型语言要高一些,但比编译型语言略低一些。
函数的声明和定义中,
不能重复定义一个参数的值;
带有默认值的形式参数必须放在参数列表的最右侧;
一、cin 函数的用法
使用cin从标准输入读取数据时,通常用到的方法有cin»,cin.get,cin.getline。
1.1 cin»的用法
(1)cin»等价于cin.operator»(),即调用成员函数operator»()进行读取数据。 (2)当cin»从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin»会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果读取成功,字符后面的分隔符是残留在缓冲区的,cin»不做处理。 (3)不想略过空白字符,那就使用 noskipws 流控制。比如cin»noskipws»input;
1.2 cin.get的用法
1.2.1 cin.get读取一个字符
读取一个字符,可以使用cin.get或者cin.get(var),示例代码如下:
#include <iostream>
using namespace std;
int main()
{
char a;
char b;
a=cin.get();
cin.get(b);
cout<<a<<b<<endl;
system("pause");
return 0;
}
输入:e[回车],输出:
注意:
(1)从结果可以看出,cin.get()从输入缓冲区读取单个字符时不忽略分隔符,直接将其读取,就出现了如上情况,将换行符读入变量b,输出时打印两次。
(2)cin.get()的返回值是int类型,成功:读取字符的ASCII码值,遇到文件结束符时,返回EOF,即-1,Windows下标准输入输入文件结束符为Ctrl+z,Linux为Ctrl+d。cin.get(char var)如果成功返回的是cin对象,因此可以支持链式操作,如cin.get(b).get(c)。
1.2.2 cin.get读取一行
读取一行可以使用istream& get ( char* s, streamsize n )或者istream& get ( char* s, size_t n, streamsize delim )。二者的区别是前者默认以换行符结束,后者可指定结束符。n表示目标空间的大小。示例代码如下:
#include <iostream>
using namespace std;
int main()
{
char a;
char array[20]={NULL};
cin.get(array,20);
cin.get(a);
cout<<array<<" "<<(int)a<<endl;
system("pause");
return 0;
}
输入:123456789[回车],输出:
注意: (1)从结果可以看出,cin.get(array,20);读取一行时,遇到换行符时结束读取,但是不对换行符进行处理,换行符仍然残留在输入缓冲区。第二次由cin.get()将换行符读入变量b,打印输入换行符的ASCII码值为10。这也是cin.get()读取一行与使用getline读取一行的区别所在。getline读取一行字符时,默认遇到’\n’时终止,并且将’\n’直接从输入缓冲区中删除掉,不会影响下面的输入处理。
(2)cin.get(str,size);读取一行时,只能将字符串读入C风格的字符串中,即char*,但是C++的getline函数可以将字符串读入C++风格的字符串中,即string类型。鉴于getline较cin.get()的这两种优点,建议使用getline进行行的读取。关于getline的用法,下文将进行详述。
1.3 cin.getline读取一行
函数作用:从标准输入设备键盘读取一串字符串,并以指定的结束符结束。 函数原型有两个:
istream& getline(char* s, streamsize count); //默认以换行符结束
istream& getline(char* s, streamsize count, char delim);
使用示例:
#include <iostream>
using namespace std;
int main()
{
char array[20]={NULL};
cin.getline(array,20); //或者指定结束符,使用下面一行
//cin.getline(array,20,'\n');
cout<<array<<endl;
system("pause");
return 0;
}
注意,cin.getline与cin.get的区别是,cin.getline不会将结束符或者换行符残留在输入缓冲区中。
*我们在平时写代码中会用到几个函数但是他们的实现功能相同,但是有些细节却不同。例如:交换两个数的值其中包括(int, float,char,double)这些个类型。在C语言中我们是利用不同的函数名来加以区分。*
void Swap1(int* a, int* b);
void Swap2(float* a, float* b);
void Swap3(char* a, char* b);
void Swap4(double* a, double* b);
*我们可以看出这样的代码不美观而且给程序猿也带来了很多的不便。于是在C++中人们提出了用一个函数名定义多个函数,也就是所谓的函数重载。*
二、cout函数用法
1、cout<< uppercase << hex<<n <<nouppercase<<" " << uppercase << n << "(H) = "
最终第二个n还是用的uppercase大写方式输出。
- 头文件#include 中有setw()设置位数,setfill(‘0’)用来设置输出格式
三、函数重载
1、函数重载定义
***函数重载*是一种特殊情况,C++允许在*同一作用域中声明几个类似的同名函数*,这些同名函数的形参列表(参数个数,类型,顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。
*在C++中不仅函数可以重载,运算符也可以重载。例如:*
*运算符«,»。既可以做移位运算符,也可以做输出,输入运算符。*
*注意:重载函数的参数个数,参数类型或参数顺序三者中必须有一个不同*
#include<Windows.h>
#include<iostream>
using namespace std;
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
float Add(float a, float b)
{
return a + b;
}
int main()
{
cout<<Add(1,2)<<endl;
cout<<Add(3.5, 4.5)<<endl;
cout << Add(2.22, 3.33) << endl;
system("pause");
return 0;
}
我们可以看到定义了一个Add函数来求三个不同类型数的和,在调用过程中系统会自动根据其实参的类型不同来实现准确调用。
#include<iostream>
#include<Windows.h>
using namespace std;
int main()
{
int max(int a, int b, int c);
int max(int a, int b);
int a = 10;
int b = 20;
int c = 30;
cout << max(a, b, c) << endl;
cout << max(a, b) << endl;
system("pause");
return 0;
}
int max(int a, int b, int c)
{
if (b > a) a = b;
if (c > a) a = c;
return a;
}
int max(int a, int b)
{
return (a > b) ? a : b;
}
从上边代码可以看出函数重载除了允许函数类型不同以外,换允许参数个数不同。
***函数重载的规则:*
- 函数名称必须相同。
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载。** **
2、函数重载的作用:
重载函数通常用来在同一个作用域内 用同一个函数名 命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
*三、函数重载是一种静态多态:*
(1)多态:用同一个东西表示不同的形态; (2)多态分为: 静态多态(编译时的多态); 动态多态(运行时的多态); (3)函数重载是一种静态多态;
*四.面试题*
*1.C语言中为什么不能支持函数重载?*
*
*
*从上图可知****编译器在编译.c文件时****,**只会给函数进行简单的重命名;具体的方法是给函数名之前加上”_”;所以加入两个函数名相同的函数在编译之后的函数名也照样相同;调用者会因为不知道到底调用那个而出错;***
**2.C++中函数重载底层是如何处理的?****
**
****
*在.cpp文件中,虽然两个函数的函数名一样,但是他们在符号表中生成的名称不一样。*
** ***‘?’表示名称开始,‘?’后边是函数名“@@YA”表示参数表开始,后边的3个字符分别表示返回值类型,两个参数类型。“@Z”表示名称结束。*** *由于在.cpp文件中,两个函数生成的符号表中的名称不一样,所以是可以编译通过的。***
*3.C++中能否将一个函数按照C的风格来编译?*
#include<iostream>
#include<Windows.h>
using namespace std;
extern "C" int Add(int a, int b)
{
return a + b;
}
int main()
{
cout << Add(10, 20) << endl;
system("pause");
return 0;
}
可以按照C风格来编译,只需在函数名前加 extern “C” 就可以完成按照C风格来编译
四、const修饰符笔记
1、const int * a 指向常量的指针
这里const 修饰的是int,而int定义的是一个整值 因此a 所指向的对象 值 不能通过 *a 来修改,但是 可以重新给 a 来赋值,使其指向不同的对象 eg: const int *a = 0; const int b = 1; int c = 1; a = &b //ok! 额外:注意不能通过a 来修改 b值 a = &c //ok! 额外:虽然c本身不是一个常量 *a = 2 //Error! 为题就在这里,不能修改通过 a 所指向的对象值,最后赋值得对象是c,因此不能通过a 来修改c值。
2、*int const a 常指针
这里const修饰的是 a ,a代表的是一个指针地址 因此不能赋给a其他的地址值,但可以修改a指向的值 这有点和cont int *a相反的意味,例子就不说了
3、至于int const *a 和 const int *a 的意义是相同的 他们两个的作用等价
补充: 4、const int * const a 这个代表a所指向的对象的值以及它的地址本身都不能被改变
3、关于const的点滴补充:
1、const 对象的地址只能赋值给指向const 对象的指针
例如 字符串常量 只能赋值给 const char*, 而不能赋值给char*
2、指向const 对象的指针可以 被赋 以 一个非const 对象的地址 3、指向const 得指针常被用作函数的形式参数,保证被传递给函数的实际对象在函数得实际对象在函数中不会被修改 4、常量在定义后就不能被修改,所以它必须被初始化。未初始化的常量定义将导致编译错误(上面都是在说明const的问题,所以没有赋值,实际语句中要赋值的)
const指针既可以指向变量,也可以指向常量;
而非const指针只能指向非const的值;
【const对象的地址只能赋值给指向const的指针】
六、内联函数
1.定义
C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。**这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。
2.使用方法
内联函数在函数定义处编写关键词inline,而并非是在函数声明处,在函数声明处写的inline会被编译器忽略掉。
因为一般写成内联函数的函数体都很短小,故就直接忽略函数的声明,直接定义函数。
3.理解
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。
所以,使用内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数。
4.何时使用内联函数?
内联不是万灵丹,它以代码膨胀(拷贝)为代价,仅仅省区了函数调用的开销,从而提高程序的执行效率。(开销指的是参数的压栈、跳转、退栈和返回操作)。
- 一方面,如果执行函数体内代码的时间比函数调用的开销大得多,那么inline效率收益会很小。
- 另一方面,每一处内联函数的调用都要拷贝代码,使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
- 如果函数体内代码比较长,使用内联将导致可执行代码膨胀过大。
- 如果函数体内出现循环或者其他复杂的控制结构,那么执行函数体内代码的时间将比函数调用的开销大得多。
七、逗号表达式
逗号表达式的求解过程是:先求解表达式1,再求解表达式2。整个逗号表达式的值是表达式2的值
又如,逗号表达式a=3 * 5,a*4,对此表达式的求解,赋值运算符的优先级别高于逗号运算符,因此应先求解a=3 * 5,经计算和赋值后得到a的值为15,然后求解a * 4,得60,整个逗号表达式的值为60(a仍为15)。
逗号表达式无非是把若干个表达式“串联”起来。即逗号表达式纯粹就是为了在只能写一条表达式的地方写多条表达式而设计的
八、x64和x86
x64即是64位编译器,x86是32位编译器
x86是CPU的架构类型,64是位数,x64:x86-64,x32:x86-32,通俗叫法。
32位编译器 char :1个字节 char(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)* short int : 2个字节 int: 4个字节 unsigned int : 4个字节 float: 4个字节 double: 8个字节 long: 4个字节 long long: 8个字节 unsigned long: 4个字节
64位编译器
char :1个字节 char(即指针变量): 8个字节* short int : 2个字节 int: 4个字节 unsigned int : 4个字节 float: 4个字节 double: 8个字节 long: 8个字节 long long: 8个字节 unsigned long: 8个字节
九、指针
1 char** a
在 char** a 语句中,a 是一个指针,这个指针(即指针 a)指向一块内存地址,该内存地址中存储的是 char* 类型的数据。指针的加减运算在这里的体现为:a + 1 表示地址加 8 字节(在 32 位系统中,地址加 4 字节)。
char* 也是一个指针,用 *a 表示,这个指针(即指针 *a)指向一块内存地址,该内存地址中存储的是 char 类型的数据。指针的加减运算在这里的体现为:(*a) + 1 表示地址加 1 字节。
说明:
- 在 32 位系统中,一个指针占用 4 字节(32 位)内存空间;在 64 位系统中,一个指针占用 8 字节(64 位)内存空间;
- 由于 a 指向一个指针类型(char*),故 a + 1 操作就是对指针类型的地址进行操作,所以 a + 1 表示地址加 8 字节;*a指向一个 char 类型,char 类型占用 1 个字节,故 *a + 1 操作就是对 char 类型的地址进行操作,所以 *a + 1 表示地址加 1 字节。
2 char* a[]
在 char* a[] 中,a 是数组,数组中的元素是指针,这些指针指向 char 类型的数据。
说明:
- 数组里面所有的元素,在内存中都是是连续存放的;
- 数组名在 C 语言中做了特殊处理,数组名使用数组所占用的(连续)内存区域的第一个字节的内存地址替代了。例如,数组占用的内存区域是 0x7fff5da3f550 到 0x7fff5da3f5a0,那么数组名 a 就会被替换成首地址 0x7fff5da3f550;
- a+1 表示数组 a 的第二个元素的内存地址,所以 a + 1 是地址加 8 字节(再次说明,因为数组 a 的元素是指针(char*),指针类型占用 8 字节);
- char* a[10] 表示限定这个数组最多可以存放 10 个指针(char*)元素,也就是说这个数组会占用 10 * 8 = 80 个字节。
3 两者区别与联系
3.1 赋值
可以使用 char* a[] 给 char** 赋值,代码如下:
char* a[] = {"hello world", "liitdar"};
char** b = a;
但不能使用 char** 给 char* a[] 赋值,因为在 char* a[] 中,a作为数组名,是一个常量,我们不能给常量赋值。
十、链表
1.结点
struct Node { int data; // 数据域(虽然这里仅有一个数据,但还是用数据datum的复数形式) Node *next; // 指针域 };
2.链表的创建
Node*& Create(Node* &head, int n, int* array) {
if (head == NULL)head = new Node{}; //这句语句让我花费了两个小时来调试,栓Q 55555`
Node* p = head; //用一个新的指针p指向head,接下来都对p进行操作,从而保证head始终指向的是链表头`
for (int i = 1;i < n;i++) {
if(1==i)p->data = array[0];
Node* pNewNode = new Node; //创建一个新的结点,之后对其赋值并连接在链表上`
pNewNode->data = array[i]; //赋值`
pNewNode->next = NULL; //使新的结点next指向NULL,保证最后一个结点也是指向NULL的`
p->next = pNewNode; //使上一个结点的next指向当前指针pNewHead`
p = pNewNode; //更新上一个结点p`
}
return head;
}
十一、类的构造函数
对于class test{
public:
private:
int a;
int b }来说
1.默认构造函数
test( ){a=1;b=0;}
在函数调用的时候,就可以直接定义一个test对象,例如 test test_a;
即是创建了一个test类的对象test_a
2.初始化构造函数
test(int x.int y){a=x;y=b;}
在函数调用的时候可以用括号去给对象赋初值,进行初始化的操作。例如 test test_b(20,,30);
3.拷贝构造函数
拷贝构造函数就是用已经存在的该类的另外一个对象去创建一个新的对象,例如:
test (const test & test_a ){`
this.a=test__a.a;`
this.b=test_a.b;}
使用的时候就可以,test test_c(test_a);
4.转换构造函数
转换构造函数即是给编译器提供了一个转换数据类型的方法,比如说是复数 类的数据和一个int型的数据相加,转换构造函数就可以将int型数据转换为复数类的对象,再根据定义的函数体进行对应的操作。
转换构造函数只有一个参数列表
test(double a){
int a=a;
int b=0;
}
使用的时候 test test_d(2.00);
十二、new的用法
new的后面写指针所指向的数据类型,告诉计算机要开辟多大的空间
#include <iostream>
using namespace std;
int example1()
{
//可以在new后面直接赋值
int *p = new int(3);
//也可以单独赋值
//*p = 3;
//如果不想使用指针,可以定义一个变量,在new之前用“*”表示new出来的内容
int q = *new int;
q = 1;
cout << q << endl;
return *p;
}
int* example2()
{
//当new一个数组时,同样用一个指针接住数组的首地址
int *q = new int[3];
for(int i=0; i<3; i++)
q[i] = i;
return q;
}
struct student
{
string name;
int score;
};
student* example3()
{
//这里是用一个结构体指针接住结构体数组的首地址
//对于结构体指针,个人认为目前这种赋值方法比较方便
student *stlist = new student[3]{{"abc", 90}, {"bac", 78}, {"ccd", 93}};
return stlist;
}
int main()
{
int e1 = example1();
cout <<"e1: "<< e1 << endl;
int *e2 = example2();
for(int i=0; i<3; i++)
cout << e2[i] << " ";
cout << endl;
student *st1 = example3();
for(int i=0; i<3; i++)
cout << st1[i].name << " " << st1[i].score << endl;
return 0;
}
十三、string、char*、char[]数据类型之间的转换
1、写了一个案例试了试
#include<iostream>
using namespace std;
int main() {
string a_str = "我是string类型数据";
string b_str("cdf");
char* c_star = new char(NULL);
c_star = "我是char*类型数据";//怎么把指针开辟空间和赋值写成一条语句呢?
char d_array[20] = "我是array类型数据";
string test_string;
char* test_star;
char test_array[20];
string test_string2;
char* test_star2;
char test_array2[20];
//char* -> string
test_string = c_star;//直接赋值
//char[] -> string
test_string2 = d_array;//直接赋值
//string -> char*
//test_star = a_str;//错误,string到char*不能直接赋值
test_star = (char*)a_str.data();//由string像char*转换要先强制转换类型为char*,由.data()方法出来的是const char*类型的数据
test_star =(char*) a_str.c_str();//.c_str()方法用法和.data()方法一样
//char[] -> char*
test_star2 = d_array;//直接赋值
//string -> char[]
//test_array = a_str;//错误,数组类型的数据不能作为左值直接被赋值
strcpy(test_array, a_str.data());
//char* -> char[]
strcpy(test_array2, c_star);
cout << "string -> char[] array:"<<test_array << endl;
cout << "char* -> char[] array:" << test_array2 << endl;
cout << "string -> char* star:" << test_star << endl;
cout << "char[] -> char* star:" << test_star2 << endl;
cout << "char* -> string string:" << test_string << endl;
cout << "char[] -> string string:" << test_string2 << endl;
return 0;
}
输出结果:
string -> char[] array:我是string类型数据
char* -> char[] array:我是char*类型数据
string -> char* star:我是string类型数据
char[] -> char* star:我是array类型数据
char* -> string string:我是char*类型数据
char[] -> string string:我是array类型数据
2.总结
string -> char[]
通过函数strcpy拷贝实现,string部分通过.data()的方法
char* -> char[]
也是通过strcpy实现
string -> char*
通过.data()或者.c_str()实现,记得要强制转换为char*
char[] -> char*
直接赋值
char* -> string
直接赋值
char[] -> string
直接赋值
十四、【项目调试】:链表的链表
1、二维数组的参数传递
对于二维数组或者更高维数组的参数传递,只能省略一维的大小。
如二维数组的参数传递应该为:int a[ ][10], 而不应该是int** a
特别是在模板函数匹配的过程中,二维数组是无法转换成二维数组的,即使强制转换类型之后,在后续的函数操作调用时,也会出现其他无法访问的错误。
2、友元函数的声明
对于在类中声明友元函数的时候,一定要在该类之前,提前声明你的友元函数或者友元类,不然编译器找不到它的…
3、模板函数的声明与定义
对于函数模板或者类模板中定义的成员函数,一定要把函数的声明和实现的描述放在头文件中!
如果只在头文件中声明了函数模板原型,在一个CPP文件中描述函数模板实现,当你在另外一个CPP文件中调用该函数模板时候,编译器会出现无法解析外部指令的报错。原因如下:
CPP采用的是分离式编译,即是说在各个编译单元编译好文件之后,再通过链接器把他们链接为一个整体。当你在一个CPP文件中调用了模板函数并且对他实例化,编译器就会在你所引用的头文件和当前编译单元中去寻找函数是如何实现的,并将它编译。如果没有找到函数的定义,但是声明是对的,会通过编译,等待在下一步链接的时候,期待在其他编译单元中的定义链接过来就可以运行。另一方面,对于函数模板定义所在的CPP文件,因为其所在的编译单元中无人调用该模板,所以并不会实例化,也不会生成相应的在另外一个CPP中需要的模板函数实例化后的函数代码。所以在程序运行调试之后,会报出链接失败的错误提示。
所以说呢,最好就是把模板函数的声明和定义都放在头文件里面。
在我调试这个项目的时候,当时没有把模板函数的定义放在头文件里面,所以编译器报出了无法解析外部指令的错误,然后我当时是修改了在模板函数中的定义,把模板去掉,直接就是定义了一个实例化之后的函数:
Node<int>* LinkList<int>::SearchNode(int date) {
Node<int>* p = head;
while (p->data != date)p = p->next;
return p;
}
像这样,然后通过了运行
但是我觉得最好还是把模板函数和模板类中成员函数的声明和定义一起放在头文件里面
4、团队的合作方法
这一次小组合作因为使劲很紧迫,在午饭之后开了个会,从具体的实现功能开始,设计函数功能,设计函数原型,最后大家一起开了个屏幕共享给写完了头文件,就散会了。下午大家根据头文件完成各个函数的定义,最后晚上九点一起联合调试。联合调试的时候写测试函数,下一次应该早点写好测试函数。这次因为数据不知道应该怎么传进去debug了好久,浪费了很多时间,下次也应该把测试函数的编写提上议程。
十五、运算符重载
1.运算符函数的调用形式
习惯调用形式 | 友元运算符重载函数调用形式 | 成员运算符重载函数调用形式 |
---|---|---|
a+b | operator+(a,b) | a.operator+(b) |
-a | operator-(a) | a.operator-() |
a++ | operator++(a,0) | a.operator++(0) |
2.运算符重载函数:成员函数?友元函数?
一般而言,对于双目运算符,将它重载为友元运算符比重载为成员运算符便于使用。对于单目运算符,则选择成员运算符较好。如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则运算符重载必须用友元函数。以下的经验可供参考:
对于单目运算符,建议选择成员函数。
对于运算符“=、()、[ ]、->” 只能作为成员函数。
对于运算符“+=、-=、/=、*=、&=、!=、~=、%=、«=、»=”,建议重载为成员函数。
对于其他运算符,建议重载为友元函数(比如+、 -、* 、 / 、 » 、 «)
小结:单目运算符++、++和 = 和迭代运算符重载为成员函数
加减乘除插入抽取 重载为友元函数
3.对于单目运算符的重载
对于++,–(后置):应该是进行自加或者自减的操作,但是返回的是原来的值。
对于++,–(前置):是直接返回自加或者自减之后的值
他们都是设计成成员函数进行操作。
由于前置和后置的运算符的重载在形式上(函数名)都是一样的,所以会在参数列表上有所区别。++或者–作为后置的时候,会有形式上的参数(int),但是没有具体的形式参数名称。
Time operator++(int) {//表示后置++
Time temp = *this;//++(后置)操作是先进行其他运算后,才会自加,所以先备份然后值返回备份,原来的数据自加就行
this->sec++;
return temp;//值返回自增前的备份,不能使用引用返回,要是引用返回的话,temp在退出这个函数的时候会释放掉,所以应该采取值返回
}
Time operator++() {//表示前置++
sec++;
return * this;
}
Time operator--(int) { //表示后置--
Time temp = *this;
sec--;
return temp;// 不能采取引用返回,因为temp的生命周期到这里就结束了
}
Time operator--() { //前置--
sec--;
return *this;
}
4.类型转换函数
函数名称:operator 类型名( ){ 具体转换的实现代码; }
函数作用:类的对象转换为其他类型的数据
与转换构造函数的联系:转换构造函数就是把别的数据类型转换为类的对象,而类型转换函数将类的对象转换为其他的数据类型
在函数名前面不能指定函数类型,函数没有参数。其返回值的类型是由函数名中指定的类型名来确定的。类型转换函数只能作为成员函数,因为转换的主体是本类的对象。不能作为友元函数或普通函数。
从函数形式可以看到,它与运算符重载函数相似,都是用关键字operator开头,只是被重载的是类型名。double类型经过重载后,除了原有的含义外,还获得新的含义(将一个Complex类对象转换为double类型数据,并指定了转换方法)。这样,编译系统不仅能识别原有的double型数据,而且还会把Complex类对象作为double型数据处理。
RMB::operator double() // 类型转换函数定义
{
return yuan + jiao / 10.0 + fen / 100.0;
}
十六、枚举类型
1.enum和enum class区别
enum和enum class最大的区别就是作用域的不同。enum的作用域在一整个文件中都存在,而enum class的作用域尽在花括号之内。这个特性就决定了enum class可以在不同的枚举类中定义相同的枚举常量,而若在enum中这样做会出现编译错误。
enum中定义的枚举常量,在其之后的代码中可以直接使用,而在enum class中定义则需要加上enum class的类名和作用域限定符。
关于枚举的一些注意:
- 枚举中每个成员(标识符)结束符是“,”而不是”;” 最后一个成员可省略”,”
- 初始化时可以赋负数,以后的标识符仍依次加1。
- 枚举变量只能取枚举说明结构中的某个标识符常量。
- 在外部可以对枚举变量进行赋值,但需要进行类型转换。
- 枚举常数可以隐式转换为int,但是int不可以隐式转换为枚举值。
- 为枚举中的每个名称分配一个整数值,该值与其在枚举中的顺序相对应。默认情况下,第一个值分配0,下一个值分配1,依次类推,但也可以显示设置枚举名称的值。
- 枚举值可以用来作判断比较。
十七、库函数
1、vector类
vector是标准库中常见的一种容器,使用起来非常方便,可以用来代替c++原本的数组。
vector 的创建和初始化
vector作为存放一串数据的容器,在创建和初始化的时候就要考虑数据类型、数据的个数以及数据的值,并且针对这几个属性就可以有几种不同的初始化方式。
vector<int> vec1;
vector<float> vec2(3);
vector<char> vec3(3,'a');
vector<char> vec4(vec3);
vector的遍历
for (int i = 0; i < vec1.size(); i++ )
cout << vec1[i] << "";
循环终止条件是i< vec.size(),这里的size()会返回vector的大小
向vector添加元素
empty()可以判断vector是否为空,而push_back()每次会添加一个元素到vector的末尾
vec1.push_back(1);
vec1.push_back(2);
从vector移除元素
pop_back()和push_back()一样,都是从vector末尾进行尾行操作。
pop_back()每次都会移除一个元素。
需要注意的是,如果vector为空,使用pop_back()将会产生异常结果,因此需要empty()来确定vector不为空。
vector相等判断与赋值
vector的赋值会把一个vector所有的元素赋值到另一个vector中,并替代所有元素;
而vector的相等也是需要逐个元素依次比较并全部相等才算相等。