C++知识点总结(一)

C++知识点总结

1.面向对象和面向过程

(1)面向过程程序设计

主要特点:主要包括数据结构+算法,分析解决问题所需要的步骤,定义函数实现各个步骤,最后调用函数解决问题

缺点:代码的可重用性和可维护性较差

(2)面向对象程序设计

概念:面向对象编程(OOP, Object Oriented Programming)其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。 面向对象是一种对现实世界理解和抽象的方法,当解决一个问题的时候,面向对象会把事物抽象成对象的概念,然后给对象赋予一些属性和方法,让每个对象去执行自己的方法。

面向对象把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。对象即为人对各种具体物体抽象后的一个概念,人们每天都要接触各种各样的对象,如手机就是一个对象。

意义:

①将日常生活中习惯的思维方式引入程序设计中

②将需求中的概念直观的映射到解决方案中

③以模块为中心构建可复用的软件系统

④可以设计出低耦合的系统,提高软件产品的灵活性、可维护性和可扩展性

面向对象的三大特性:封装、继承、多态

封装 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

继承 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。通过继承创建的新类称为「子类」或「派生类」,被继承的类称为「基类」、「父类」或「超类」。 要实现继承,可以通过 继承和组合 来实现。

多态 多态是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单说就是一句话:允许将子类类型的指针赋值给父类类型的指针。 实现多态,有两种方式,覆盖和重载。两者的区别在于:覆盖在运行时决定重载是在编译时决定并且覆盖和重载的机制不同

 

2.C到C++的升级

相比C语言,C++中有新增的语法和关键字,有类和对象的概念,有重载和重写的概念,有模板的概念,有STL库等等。本节主要总结在C++中,一些C语言和C++共有的关键字、运算符等的升级。

2.1 struct

C语言中的struct为结构体,定义了一组变量的集合,C语言中struct定义的标识符并不是一种新的类型。

C++中的struct用于定义一个全新的类型。例如:

    struct Student
    {
      const char* name;
      int age;
    }

在C语言中,定义一个struct变量写法为struct Student s。

在C++中,定义一个struct变量写法为Student s。

2.2 const

C语言中的const修饰的变量是只读的,本质还是变量,并会为变量分配存储空间。

C++中的const声明时在符号表中放入常量,可以实现真正意义上的常量,一般不会为const常量分配存储空间,除非使用&操作符取地址或使用extern,表明需要在其它文件中使用。

例如:

 const int n = 5;
 int *p = (int*)&n;
 *p = 1;
 printf("n = %d\n", n);
/*
*C语言编译器运行结果:n = 1;
*C++编译器运行结果:n = 5;
*/

更多关于const的总结将在后续章节列举。

2.3 其它升级

(1)C语言中可以重复定义多个同名的全局变量,而C++中不允许定义多个同名的全局变量。

(2)C语言中可以使用默认类型,但C++不行,C++中的任意标识符都必须显式的指明类型,例如:

 func1(i){ return i };  //在C语言里没有函数类型、没有返回值的函数默认为int类型,在C++中不合法
 func2(i){ return 5 };  //在C语言里为int类型,在C++中不合法

(3)int f()和int f(void)

/* .c */  
 int f()        //表示返回值为int,接收任意参数的函数
 int f(void)    //表示返回值为int的无参函数
/* .cpp */ 
 int f()        //表示返回值为int的无参函数
 int f(void)    //表示返回值为int的无参函数

(4)三目运算符

C语言的三目运算符返回的是变量值,不能作为左值使用。

C++的三目运算符返回的是变量本身,既可作为右值,又可作为左值使用(如果有常量值则不能作为左值)。当三目运算符的可能返回都是变量时,返回的是变量引用,当有常量时,返回的是值。例如:

int a = 1;
int b = 2;
​
(a < b ? a : b) = 3;  //正确,返回a或b的引用,可作为左值
(a < b ? 1 : b) = 4;  //错误,返回1或b的值,不可作为左值

(5)register关键字请求编译器将局部变量存储于寄存器中,在C语言中无法取得register变量的地址,C++中可以取得register变量的地址。

 

3.指针

3.1 指针的使用和本质分析

1.初学指针使用注意事项

1)指针一定要初始化,否则容易产生野指针(后面会详细说明);

2)指针只保存同类型变量的地址,不同类型指针也不要相互赋值;

3)只有当两个指针指向同一个数组中的元素时,才能进行指针间的运算和比较操作;

4)指针只能进行减法运算,结果为同一个数组中所指元素的下表差值。

2.指针的本质分析

(1)指针是变量,指针*的意义:

1)在声明时,*号表示所声明的变量为指针。

例如:int n = 1; int* p = &n;

这里,变量p保存着n的地址,即p<—>&n,*p<—>n

2)在使用时,*号表示取指针所指向变量的地址值。

例如:int m = *p;

(2)如果一个函数需要改变实参的值,则需要使用指针作为函数参数(传址调用),如果函数的参数数据类型很复杂,可使用指针代替。

最常见的就是交换变量函数void swap(int* a, int* b)

(3)指针运算符*和操作运算符的优先级相同

例如:int m = *p++;

等价于:int m= *p; p++;

2.指针和数组

1.指针、数组、数组名

如果存在一个数组 int m[3] = {1,2,3};

定义指针变量p,int p = m(这里m的类型为int,&a[0]==>int)

其中,&m为数组的地址,m为数组0元素的地址,两者相等,但意义不同,例如:

m+1 = (unsigned int)m + sizeof(*m)
​
&m+1= (unsigned int)(&m) + sizeof(*&m) 
​
    = (unsigned int)(&m) + sizeof(m)

m+1表示数组的第1号元素,&m+1指向数组a的下一个地址,即数组元素“3”之后的地址。

等价操作:

 *m[i]←→(m+i)←→(i+m)←→i[m]←→(p+i)←→p[i]**

实例测试如下:

#include<stdio.h>
​
int main()
{
    int m[3] = { 1,2,3 };
    int *p = m;
​
    printf(" &m = %p\n", &m);
    printf(" m = %p\n", m);
    printf("\n");
​
    printf(" m+1 = %p\n", m + 1);
    printf(" &m[2] = %p\n", &m[2]);
    printf(" &m+1 = %p\n", &m + 1);
    printf("\n");
​
    printf(" m[1] = %d\n", m[1]);
    printf(" *(m+1) = %d\n", *(m + 1));
    printf(" *(1+m) = %d\n", *(1 + m));
    printf(" 1[m] = %d\n", 1[m]);
    printf(" *(p+1) = %d\n", *(p + 1));
    printf(" p[1] = %d\n", p[1]);
​
    return 0;
}
​
/*输出结果如下:
&m = 00AFF8AC
m = 00AFF8AC
​
m+1 = 00AFF8B0
&m[2] = 00AFF8B4
&m+1 = 00AFF8B8
​
m[1] = 2
*(m+1) = 2
*(1+m) = 2
1[m] = 2
*(p+1) = 2
p[1] = 2
*/

2.数组名注意事项

1)数组名跟数组长度无关;

2)数组名可以看作一个常量指针; 所以表达式中数组名只能作为右值使用;

3)在以下情况数组名不能看作常量指针:

  • 数组名作为sizeof操作符的参数

  • 数组名作为&运算符的参数

3.指针和二维数组

一维数组的指针类型是 Type,二维数组的类型的指针类型是Type[n]

4.数组指针和指针数组

①数组指针

1)数组指针是一个指针,用于指向一个对应类型的数组;

2)数组指针的定义方式如下所示:

int (*p)[3] = &m;

②指针数组

1)指针数组是一个数组,该数组里每一个元素为一个指针;

2)指针数组的定义方式如下所示:

int* p[5];

3.指针和函数

1.函数指针

函数的本质是一段内存中的代码,函数的类型有返回类型和参数列表,函数名就是函数代码的起始地址(函数入口地址),通过函数名调用函数,本质为指定具体地址的跳转执行,因此,可定义指针,保存函数入口地址,如下所示:

int funcname(int a, int b);
int(*p)(int a, int b) = funcname;

上式中,函数指针p只能指向类型为int(int,int)的函数

2.函数指针参数

对于函数int funcname(int a, int b);

普通函数调用 int funcname(int, int),只能调用函数int func(int, int)

函数指针调用 intname(*func)(int,int),可以调用任意int(int,int)类型的函数,从而利用相同代码实现不同功能,

假设有两个相同类型的函数func1和func2,普通函数调用和函数指针调用方法如下:

printf("普通函数调用\n");
printf("func1 = %d\n", func1(100, 10, 1));
printf("func2 = %d\n", func2(100, 10, 1));
printf("\n");
​
printf("函数指针调用\n");
int(*p)(int, int, int) = NULL;
p = func1;
printf("p = %d\n", p(100, 10, 1));
p = func2;
printf("p = %d\n", p(100, 10, 1));
printf("\n");

需要注意的是,数组作为函数参数的时候,会变为函数指针参数,即:

int funcname( int m[] )<——>int funcname ( int* m );

调用函数时,传递的是数组名,即

funcname(m);

3.回调函数

利用函数指针,可以实现一种特殊的调用机制——回调函数。回调函数是指把需要调用的函数指针作为参数传递给另一个函数,这个指针会在适当的时机被用来调用其所指向的函数,该函数指针指向的函数可能有多个,这种机制具有非常大的灵活性。

所谓的回调指的是,上层调用底层,底层又回来调用上层,回调函数的机制:

1)调用者不知道需要调用的具体函数

2)被调用的函数不知道何时会被调用

3)在满足特定条件时,调用者通过函数指针调用被调用的函数

一个简单的回调函数的例子如下所示:

#include<stdio.h>
​
int func1(int a, int b)
{
    int num = 2 * a * b;
    printf("func1的num = %d\n", num);
    return num;
}
​
int func2(int a, int b)
{
    int num = 4 * a * b;
    printf("func2的num = %d\n", num);
    return num;
}
​
//回调函数
int callback(int(*p)(int, int))
{
    int a = 3, b = 2;
    return p(a, b);
}
​
int main()
{
    callback(func1);
    callback(func2);
}
​
//可以看出,如果不使用函数指针,而使用普通函数调用,则无法使用callback函数来调用func1和func2两个函数。

4.野指针和悬空指针

指针没有初始化就被使用,或者指针所指向的变量在指针之前被销毁,就会产生野指针。

已经释放过的指针,被称为悬空指针。

使用者两种指针都会引发内存错误。

 

4.引用

4.1 引用的概念

引用可以看做是变量的一个别名,通过这个别名和原来的名字都能够找到变量。引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它数据,这有点类似于常量(const 变量)

注意:引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址。

在C++中,引用只是对指针进行了简单的封装,它的底层依然是通过指针实现的,C++编译器在编译过程中使用指针常量作为引用的内部实现,即 Type& varname <——> Type* const name因此引用所占用的内用空间大小与指针相同

4.2 引用的使用场景

引用除了作为变量的别名之外,还可以作为函数的参数和返回值。

1.引用作为函数参数

在定义或声明函数时,可以将函数的形参指定为引用的形式,这样在调用函数时就会将实参和形参绑定在一起,让它们都指代同一份数据。如此一来,如果在函数体中修改了形参的数据,那么实参的数据也会被修改,从而拥有“在函数内部影响函数外部数据”的效果。

2.引用作为函数返回值

引用还可以作为函数返回值,例如:

int &func(int &para1, int &para2);

在将引用作为函数返回值时应该注意一个小问题,就是不能返回局部数据(例如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在了,C++编译器检测到该行为时也会给出警告。

4.3 引用和指针的区别

1.区别

C++中引用具有操作简单且功能强大、避免指针操作错误引发的内存问题等特点,所以在大多数情况下引用可以代替指针。引用和指针的主要区别如下:

(1)指针是一个变量,引用只是一个变量的别名。引用必须在定义时初始化,并且以后也要从一而终,不能再指向其他数据;而指针没有这个限制,指针在定义时不必赋值,以后也能指向任意数据。

(2)引用不可指向空值,指针可以。

(3)指针可以有多级,但是引用只能有一级,例如,int **p 是合法的,而 int &&r 是不合法的。

(4)sizeof指针得到的是指针的大小,sizeof引用得到的是引用绑定变量的大小

(5)指针和引用的自增(++)自减(--)运算意义不一样。对指针使用 ++ 表示指向下一份数据,对引用使用 ++ 表示它所指代的数据本身加 1;自减(--)也是类似的道理。

2.使用场景

(1)需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的。

(2)类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式。

(3)当需要指向某个东西,而且一定专一,绝不会让其指向其它东西,例如有些函数参数为了避免拷贝可以使用引用,或者实现一个操作符而其语法需求无法由指针达成时,使用引用。大多数情况下还是使用指针。

 

5.const总结

5.1 const含义

const可以修饰基本数据类型,也可以修饰指针变量、引用变量、函数、类对象等。C++中的const可以定义真正意义上的常量。

5.2 const与#define

C++中的const常量与宏定义#define类似,但又有所区别。#define只是单纯的文本替换,预处理时就已经替换掉了,而const则是在编译时替换的,并且编译器还会对const常量进行类型和作用域的检查。

5.3 const与volatile、mutable

volatile关键字一样,是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。被volatile修饰的const常量不会进入符号表,不是真正意义上的常量。使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据。

volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份多线程中被几个任务共享的变量需要定义为volatile类型

mutable和const是反义词,在C++中是为了突破const的限制而设置的,被mutable修饰的变量,将永远处于可变的状态。例如,在const成员函数里的非静态成员变量本身是不可修改的,但如果加上了mutable修饰符,该变量在const函数里也可以修改。

示例:

class A
{
    mutable int f1;
    int f2;
public:
    void func() const
    {
        f1 = 10;    //ok, mutable修饰的变量在const函数里也可以修改
        f2 = 5;     //error, const函数里不可修改变量
    }
}

5.4 const与指针

int const* p ; (指针p本身是变量,p指向的数据为常量

const int* p;(指针p本身是变量,p指向的数据为常量

int* const p;(指针p本身是常量,p指向的数据为变量

const int* const p;(指针p和p指向的数据都是常量

巧记法:左数右指,即const在星号左边时,指针本身是变量,指针指向的数据是常量;const在星号右边时,指针本身是常量,指针指向的数据是变量

5.5 const与引用

C++中,const引用让变量具有只读属性,定义示例如下:

const int& b = a;    //const引用让变量拥有只读属性

不能用常量对引用进行初始化,但是可以对const引用进行初始化。 当使用常量对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名。

5.6 const与函数

const可以修饰函数的参数,也可以修饰函数的返回值。修饰函数的参数时,该参数在函数内部不可修改,修饰函数的返回值时,函数的返回值不可修改,如下所示:

//修饰函数参数
int func1(const int &a)
{
    a = 5;   //error,const修饰的函数参数在函数内部不可修改
}
//修饰函数的返回值
const int func2()
{
    return 0;    //函数返回值不可修改  
}

5.7 const成员函数、const成员变量和const对象

const可以修饰类的成员函数和成员变量。

1.const成员函数

修饰类的成员函数时,该函数除了静态成员之外的其它成员不可修改,const 成员函数也称为常成员函数。同时const成员函数只能调用const成员函数。如下所示:

class A
{
public:
    void func1() const
    {
        a = 10;    //ok
        b = 5;     //error,不可修改非静态成员变量
    }
    void func2()
    {
        
    }
    void func3() const
    {
        func1();    //ok
        func2();    //error,不可调用非const成员函数
    }
private:
    static int a;
    int b;
}

2.const成员变量

修饰类的非静态成员变量时,该变量只能在构造函数中初始化;

修饰类的静态成员变量时,则需要在类的外部初始化。

如下所示:

class B
{
public:
    B():b(5)
    {
        
    }
private:
    static const int a;
    const int b;
}
​
const int B::a = 10;

3.const对象

const修饰的对象成为只读对象,一旦将对象定义为const对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)了。

 

6.static关键字总结

6.1 static修饰局部变量

static修饰的局部变量称为静态局部变量,只会被初始化一次。

普通局部变量存放在栈区,静态局部变量存放在静态数据区。

普通局部变量的生命周期在语句或者函数执行结束时结束,静态局部变量会一直持续到程序结束。

6.2 static修饰全局变量和函数

static修饰的全局变量称为静态全局变量。普通全局变量的作用域可以是本文件,也可以是其它文件(添加extern声明),静态全局变量的作用域只能是本文件。

6.3 static修饰函数

与全局变量类似,staitc修饰的函数的作用域只能是本文件。

6.4 static成员变量和static成员函数

1.static成员变量

static修饰的成员变量称为静态成员变量,存放在全局数据区,需要在类外单独定义。static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用

静态成员变量属于整个类所拥有,可以被任意成员函数访问,且生命周期不依赖于任何对象。

静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。

示例如下:

class A
{
public:
    static int test;  //静态成员变量
};
​
int A::test = 5;    //类外初始化
​
//通过类访问 static 成员变量
A::test = 10;
//通过对象来访问 static 成员变量
A a;
a.test = 20;
//通过对象指针来访问 static 成员变量
A *pa = new A();
pa -> test = 30;

2.static成员函数

static修饰的成员函数称为静态成员函数,静态成员函数属于整个类所有,静态成员函数没有this指针,只能访问静态成员,但非静态成员函数可以访问静态成员函数

静态成员函数既可以通过对象名访问,也可以通过类名访问

静态成员函数不能被声明为const、volatile和虚函数

示例如下:

class A
{
public:
    static void func1()
    {
        a = 1;     //ok
        b = 2;     //error,静态成员函数只能访问静态成员
    }
    void func2()
    {
        func1();    //ok
    }
    static void func3() const;      //error,静态成员函数不能加const限定符
    virtual static void func4();    //errir,静态成员函数不能被声明为虚函数
private:
    static int a;
    int b;
};

 

7.C++ inline关键字

C++中使用内联函数代替宏定义#define,用inline关键字声明内联函数。相比于宏定义,内联函数会进行参数类型检查,且具有返回值。内联函数在编译时直接将函数代码嵌入到目标代码中,省去函数调用的开销来提高效率,并可以实现重载。

注意,要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。例如:

inline void swap(int *a, int *b);  //只是在声明处添加inline关键字,编译器会忽略
​
inline void swap(int *a, int *b)   //需要在定义处添加inline关键字
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

 

8.C++强制类型转换

C++提供了四种强制类型转换,分别是static_cast、const_cast、reinterpret_cast和dynamic_cast。

语法如下:

xxx_cast<newType>(data)
//newType 是要转换成的新类型,data 是被转换的数据。

1.static_cast

用于良性转换,这样转换的风险较低,一般不会发生什么意外,例如:

  • 原有的自动类型转换,例如 short 转 int、int 转 double、const 转非 const、向上转型等;

  • void指针和具体类型指针之间的转换,例如void *int *char *void *等(不能用于基本类型指针间的转换);

  • 有转换构造函数或者类型转换函数的类与其它类型之间的转换,例如 double 转 Complex(调用转换构造函数)、Complex 转 double(调用类型转换函数)。

static_cast 不能用来去掉表达式的 const 修饰和 volatile 修饰。换句话说,不能将 const/volatile 类型转换为非 const/volatile 类型。

2.const_cast

const_cast 用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非const/volatile 类型

强制转换的目标类型必须是指针或引用,例如:

const int n = 100;
const_cast<int*>(&n);

3.reinterpret_cast

reinterpret_cast 主要用于两个基本类型指针之间的转换,以及int 和指针之间的转换。reinterpret_cast是高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。

4.dynamic_cast

dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。

 

9.new/delete和malloc/free

9.1 new/delete和malloc/free的区别

(1)malloc/free是C语言库函数,new/delete是C++操作符,两者的功能都是从堆空间申请和回收数据。区别在于malloc申请得到的是未初始化的空间,new得到的是初始化过后的空间。

执行new实际上执行两个过程:

1)调用::operator new,分配未初始化的内存空间(malloc),出现异常则抛出std::bad_alloc异常;

2)使用对象的构造函数对空间进行初始化,出现异常则自动调用delete释放。

执行delete实际上执行两个过程:

1)使用析构函数(Obj::~Obj())对对象进行析构

2)调用::operator delete回收内存空间(free)

(2)malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。

(3)在对非基本数据类型的对象使用的时候,对象创建的时候还需要执行构造函数,销毁的时候要执行析构函数。而malloc/free是库函数,是已经编译的代码,无法做到这一点。

9.2 malloc、calloc、realloc的区别

以上三个函数的定义如下:

void *malloc(size_t size);
// size为申请空间的大小
​
void *calloc(size_t n, size_t size);
// size为单个类型大小,n为申请的个数,最后申请空间大小为n×size
​
void *realloc(void *p, size_t new_size);
// p为堆上已经存在的空间地址,new_size为额外的空间

malloc和calloc的区别在于:申请空间的方式不同,且malloc函数得到的内存空间是未初始化的,calloc函数得到的内存空间是经过初始化的,其内容全为0

realloc函数的作用为:修改已经申请的内存的大小,用于扩充容量。

如果new_size小于或等于p之前指向的空间大小,那么。保持原有状态不变。如果new_size大于原来p之前指向的空间大小,那么,系统将重新为p从堆上分配一块大小为new_size的内存空间,同时,将原来指向空间的内容依次复制到新的内存空间上,p之前指向的空间被释放。relloc函数分配的空间也是未初始化的

 

9.3 new/delete、new[]/delete[]、allocator

new主要用于申请一个变量,new[]主要用于申请数组。

delete主要用于释放一个变量,只会调用一次析构函数,delete[]主要用于释放数组,会逆序调用数组中每个元素的析构函数。

new和delete时都进行两步操作,new包括内存申请和对象构造,delete包括对象析构和内存释放。allocator则是将两步分开,申请时不进行初始化,只有使用的时候才会初始化。

 

10.重载

10.1 函数重载

1.基本概念

函数重载的概念为:用同一个函数名定义不同的函数。

函数重载至少满足以下条件中的一个条件:

  • 参数个数不同

  • 参数类型不同

  • 参数顺序不同

重载函数的函数类型是不相同的,编译器根据同名函数和参数列表判断重载,会将所有同名函数中根据以上条件寻找并精确匹配实参,注意正常情况下,函数返回值不能作为重载的依据。重载示例如下:

int func(int a, double b, char c);
int func(int a, double b);            //参数个数不同
int func(int a, double b, double c);  //参数类型不同
int func(int a, char c, double b);    //参数顺序不同

2.函数重载和函数指针

正常情况下,函数重载时,编译器仅通过参数列表(参数个数、参数类型、参数顺序)来进行函数选择,而不需要将函数返回值作为重载的依据,但当有函数指针时,函数返回值(即函数类型)就需要作为函数重载的依据。示例如下:

int func(int a, int b, int c = 0)
{
    ...
}
int func(int a, int b)
{
    ...
}
​
int main()
{
    func(1,2);   //error func函数中有默认参数,两个参数类型都为(int,int),编译器无法识别
    //用函数指针调用
    int(*p1)(int a, int b, int c);
    int(*p2)(int a, int b);
    p1(1, 2, 3);
    p2(1, 2);
}

3.类中的函数重载

类中的成员函数、静态成员函数、构造函数可以被重载。但类中的函数不能和类外的函数构成重载关系,因为函数重载只能发生在同一作用域中。

 

10.2 运算符重载

运算符重载可以扩展操作符的功能,主要通过operator关键字实现,语法格式如下:

Type operator xxx(const Type &a, const Type &b)
{
    ...
}
//xxx表示已经存在的运算符

运算符重载的注意事项:

①重载后运算符的含义应该符合原有用法习惯。例如重载+运算符,完成的功能就应该类似于做加法,在重载的+运算符中做减法是不合适的。此外,重载应尽量保留运算符原有的特性。

②C++ 规定,只能重载已有的运算符,不能创造新的运算符,并且操作符重载不能改变运算符的优先级,不能改变运算数的个数。

③以下运算符不能被重载:.、.*、::、? :、sizeof。

④ 运算符可以重载为全局函数, 然后声明为类的友元。此时函数的参数个数就是运算符的运算数个数,运算符的运算数就成为函数的实参。 运算符也可以重载为成员函数。此时函数的参数个数就是运算符的操作数个数减一,少的这个参数是左运算数,而成员函数的this参数就正好可以作为左运算数。

⑤重载运算符()、[]、->、或者赋值操作符=时,只能将它们重载为成员函数,不能重载为全局函数

⑥运算符重载的本质为函数定义, 使用运算符的表达式就被解释为对重载函数的调用。

 

11.字符串

C++中提供了string类型用于表示字符串,封装了字符串的各种操作

1.C++输入输出流

图中这些流类各自的功能分别为:

  • istream:常用于接收从键盘输入的数据;

  • ostream:常用于将数据输出到屏幕上;

  • ifstream:用于读取文件中的数据;

  • ofstream:用于向文件中写入数据;

  • iostream:继承自 istream 和 ostream 类,因为该类的功能兼两者于一身,既能用于输入,也能用于输出;

  • fstream:兼 ifstream 和 ofstream 类功能于一身,既能读取文件中的数据,又能向文件中写入数据。

C++ cin输入流常用成员方法如表所示:

成员方法名 功能
getline(str,n,ch) 从输入流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 '\0'。
get() 从输入流中读取一个字符,同时该字符会从输入流中消失。
gcount() 返回上次从输入流提取出的字符个数,该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。
peek() 返回输入流中的第一个字符,但并不是提取该字符。
putback(c) 将字符 c 置入输入流(缓冲区)
ignore(n,ch) 从输入流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。
operator>> 重载 >> 运算符,用于读取指定类型的数据,并返回输入流对象本身。

C++ cout输出流常用成员方法如表所示:

成员方法名 功能
put() 输出单个字符。
write() 输出指定的字符串。
tellp() 用于获取当前输出流指针的位置。
seekp() 设置输出流指针的位置。
flush() 刷新输出流缓冲区。
operator<< 重载 << 运算符,使其用于输出其后指定类型的数据。

2.字符串输入输出流

C++标准库中提供了字符串流类(sstream)用于string的转换,示例如下:

/*
 * istringstream : 字符串输入流,可将string转化为数字
 * ostringstream : 字符串输出流,可将数字转化为string
 */
#include<sstream>
​
//string转换为数字
istringstream iss("99.5");
double num;
iss >> num;
​
//数字转换为string
ostringstream oss;
oss << 99.5;
string s = oss.str();
posted @ 2021-10-31 21:01  烟消00云散  阅读(522)  评论(0编辑  收藏  举报