学习笔记:C++基础概念总结
These my learning notes about the C++ language
1、变量有哪几种类型?
auto
存储类型:采用栈堆方式分配内存空间,属于一时性存储,其存储空间可以被若干变量多次覆盖使用。register
存储类型:存放在通用寄存器中externa
存储类型:所用函数和程序段中都可以引用static
存储类型:在内存中是以固定地址存放的,在整个程序运行期间都有效。
2、什么是内联函数,它有那些特点?
定义时使用关键字 inline 的函数叫内联函数,编译器在编译时在调用处用函数体进行替换,节省了参数传递,控制转移等开销,内联函数体内不能有循环语句和 switch 语句,内联函数的定义必须出现在内联函数第一次调用之前,对内联函数不能进行异常接口声明。
3、调用重载函数时,通过什么来区分被调用的是哪个函数?
重载函数的函数名是相同的,但它们的参数的个数和类型不同,编译器根据实参和形参的类型和个数的最佳匹配,自动确定调用那一个函数。
4、公有类型成员和私有类型成员有什么区别?
公有类型成员用public
关键字声明,共有类型定义了类的外部接口;私有类型的成员用private
关键字声明,只允许本类的函数成员来访问,而类外部的任何访问都是非法的,这样,私有成员就整个隐蔽在类中,在类的外部根本就无法被看到,实现了访问权限的有效控制。
数据成员和成员函数都可以为公有的或私有的,但数据成员最好是声明为私有的。
5、protected关键字有何作用?
protected
用来声明保护类型的成员,保护类型的性质和私有类型的性质相似,其差别在于继承和派生时派生类的成员函数可以访问基类的保护成员。
6、构造函数和析构函数用什么作用?
构造函数的作用就是在对象被创建时利用特定的值构造对象,讲对象初始化一个特定的状态,使此对象具有区别于彼对象的特征,完成的就是一个从一般到具体的过程,构造函数在对象创建的时候由系统自动调用。
析构函数与构造函数的作用几乎正好相反,它是用来完成对象被撤出前的一些清理工作,一般情况下,析构函数是在对象的生存周期即将结束的时刻由系统自动调用,它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
7、什么是拷贝构造函数?
拷贝构造函数是一种特殊的构造函数,其形参是本类的对象的引用,其作用是使用一个已经存在的对象,去初始化一个新的同类的对象。在以下三种情况下会被调用:
当用类的一个对象去初始化该类的另一个对象时;
如果函数的形参是类对象,调用函数进行形参和实参结合时;
如果函数的返回值是类对象,函数调用完成返回时。
8、静态数据成员和普通数据成员的区别?
普通数据成员属于类的一个具体的对象,只有对象被创建了,普通数据成员才会被分配内存。而静态数据成员属于整个类,即使没有任何对象创建,类的静态数据成员变量也存在。因为类的静态数据成员的存在不依赖与于任何类对象的存在,类的静态数据成员应该在代码中被显示的初始化,一定要在类外进行。外部访问类的静态成员只能通过类名来访问。类的静态成员函数无法直接访问普通数据成员(可以通过对象名间接的访问),而类的任何成员函数都可以访问类的静态数据成员。静态成员和类的普通成员一样,也具有public、protected、private
三种访问级别,也可以具有返回值、const
修饰符等参数。
9、关于静态成员函数
静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针
。静态成员函数不可以同时声明为 virtual、const、volatile
函数。
10、友元函数和友元类
友元是不同的类之间、类与类外的函数之间的共享数据的机制。使用friend关键字声明,友元函数可以访问相应类的保护成员和私有成员。友元类它的所有成员函数都是相应类的友元函数。友元关系不具有交换性、传递性,也不能被继承。
11、运算符“*”和运算符“&”的作用是什么?
*
称为指针运算符,是一个一元运算符,表示指针所指向的对象的值;&
称为取地址运算符,也是一个一元操作符,是用来得到一个对象的地址。
个人认为它们是一对作用相反的运算符。
12、引用和指针
引用是给另外一个变量起别名,所以引用不会分配内存空间。声明方法:(类型标识符 &引用名=目标变量名;(如int &r = num;)
)。指针是一个存放地址的变量,需要分配内存空间,声明方法:(类型标识符 *指针名 = 目标变量地址(int * p = &a;)
)。对于声明后的指针,p指向的是变量a的地址,*p 表示的是变量a的内容。在C++中使用引用代替指针可以提高程序的安全性,但当需要对变量重新赋值以另外的地址或赋值为NULL时只能使用指针。
13、const int * p1 和 int * const p2 的区别
const int * p1
声明了一个指向整型常量的指针p1,因此不能通过指针p1来改变它所指向的整型值;int * const p2
声明了一个指针型常量,用于存放整型变量的地址,这个指针一旦初始化后,就不能被重新赋值。
(理解方法:主要是参考const
修饰的谁)。
14、空指针和野指针
空指针:一般声明一个指针变量赋值为NULL,这就是空指针,各个类型的空指针都存在确确实实的内存地址,但是不会指向任何有效的值的内存地址,对空指针操作,例如访问属性和方法,会抛出空指针异常,因为空指针指向的内存地址没有对应的物理地址。
野指针:指那些释放内存,但是指针赋值为空,这时候的指针指向任意地址,好可怕,例如指向内核地址或不属于本程序的内存地址,程序会被kill,即奔溃。
内存泄漏:分为堆泄露和资源泄露 两种,内存分配失败或者内存分配成功却没有指针指向它(即无法操作该内存),会导致内存分配的越来越多,导致系统内存不够而终止程序。
15、C/C++ 内存管理需要遵循的规则
用malloc
或者 new
申请内存之后,应该立即检查指针值是否为 NULL ,防止使用指针值为NULL的内存;
不要忘记数组和动态内存赋初值,防止未被初始化的内存作为右值使用;
避免数组或者指针下标越界,特别要当心“多1”或者“少1”的操作;
动态内存的申请与释放必须配对,防止内存泄露;
用free
或者delete
释放了内存之后,立即将指针设置为NULL,防止产生“野指针”;
16、关于继承与派生
首先要明确从基类继承的成员的访问控制属性受两方面因数的影响(1、成员在基类中原来声明的访问控制属性;2、继承方式)。在运用经常关系时,要注意基类的构造函数和析构函数都不能继承,但是在建立派生类对象时基类的构造函数会被自动调用,派生类消亡时,会自动调用基类的析构函数。在多继承的情况下,如果存在公共基类,就会出现成员标识二义性的问题,这是将公共基类作为虚基类继承是一个比较好的解决方案(虚基类继承的语法格式:class 派生类名:virtual 继承方式 基类名
)。
17、关于多态性
多态是指同样的消息被不同类型的对象接收时导致完全不同的行为,是对类的特定成员函数的再抽象。C++支持的多态有多种类型,重载(包括函数重载和运算符重载)和虚函数是其中主要的方式。
18、关于抽象类
带有纯虚函数的类是抽象类,抽象类的主要作用是通过它为一个类族建立一个公共的接口,使它们能够有效的发挥多态特性。抽象类声明了一组派生类共同操作接口的通用语义,而接口的完整实现,即纯虚函数的函数体,要由派生类自己给出。但抽象类的派生类并非一定要给出纯虚函数的实现,如果派生类没有给出纯虚函数的实现,这个派生累仍然是一个抽象类。
19、虚构造函数和虚析构函数
在C++中,不能声明虚构造函数,多态是不同的对象对同一消息有不同的行为特性,虚函数作为运行过程中多态的基础,主要针对对象的,而构造函数是在对象产生之前运行的,因此虚构造函数是没有意义的。可以虚析构函数,析构函数的功能是在该类对象消亡之前进行一些必要的清理工作,如果一个类的析构函数是虚函数,那么由它派生而来的所有子类的析构函数也是虚函数,析构函数设置为虚函数之后,在使用指针引用时可以动态连编,实现运行时的多态,保证使用基类的指针就能够调用适当的析构函数针对不同的对象进行清理工作。
20、STL的容器、迭代器和算法概念
STL的容器库包括7种基本容器:向量(vector)、双向队列(deque)、列表(list)、集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)。这7种容器可以分为两种基本类型:顺序容器好关联容器。
STL根据迭代器的功能,将它们分为5类:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。
STL标准模板库中的算法大致分为4类:非可变序列的算法(不会改变容器的内容)、可变序列的算法、排序相关的算法、通用数值算法。
21、流类库与输入/输出
I/O 流类库是一个提供输入/输出功能的、面向对象的类库。
流是对输入/输出的一个抽象表述、程序通过从流中提取字符和向流中插入字符来实现输入输出。一般来说、流是与实际字符源或目标相关的,例如磁盘文件、键盘或显示器,所以对流进行的提取或插入操作实际上就是对物理设备的操作。
标准输入/输出流对象时连接程序与标准输入/输出设备的,常用的标准输入流有cin
,标准输出流有:cout、cerr、clog
。标准流对象都是在<iostream>
中预先声明好的。
22、异常处理
当一个函数在执行的过程中出现了一些不平常的情况,或运行结果无法定义的情况,使得操作不得不被中断时,这就是所谓的异常。异常通常是用throw关键字产生的一个对象,用来表明出现了一些意外的情况。C++的异常处理机制有很多优点,可以使的异常的引发和处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。C++提供了三个关键字来对异常进行处理:
try
:可能抛出异常的程序段必须以try开。紧跟着try的是一段包含在大括号中的程序,这段程序有可能抛出异常。
throw
:异常要通过关键字throw来抛出。异常对象的类型决定那一个catch语句可以捕获这一异常。
catch
:处理异常的程序必须以catch开始。跟随在catch后面的是一段包含在大括号中的程序。
23、C/C++宏定义
#define
是C语言提供的宏定义命令,其主要目的是使程序编写规范,修改调试容易,并在一定程度上提高程序的运行效率。使用#define
命令将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。
24、lambda函数
C++中lambda函数是作为C++11新特新添加到C++中的,其主要是以匿名函数捕获scope内变量的方式构造闭包(closure)。相关标准参考:c++ reference