前几天复试的时候有的同学说面试的时候有问道C++的基础知识有点懵。因此,我们将加入新的C++专题,当然数据结构考点专题依然会同步更新。这篇文章主要介绍一些基本的C++知识点,是大家能够很好的从C过度到C++。例如,类成员的方位范围、引用,动态内存分配、内联函数、函数重载、静态成员函数等。
0导言C语言使用结构化程序设计:程序 = 数据结构 + 算法。但在结构化程序设计中,函数和其所操作的数据结构,没有直观的联系。并且结构化程序设计没有“封装”和“隐藏”的概念。要访问某个数据结构中的某个变量,就可以直接访问,那么当该变量的定义有改动的时候,就要把所有访问该变量的语句找出来修改,十分不利于程序的维护、扩充。面向对象的程序设计方法,能够较好解决上述问题:面向对象的程序 = 类 + 类 + …+ 类。C++中,类的名字就是用户自定义的类型的名字。可以象使用基本类型那样来使用它。设计程序的过程,就是设计类的过程。我们之前已经介绍过python面向对象方法,因此,对于面向对象程序应该并不陌生。
面向对象的程序设计方法将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性);将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构(这一步叫“抽象”)。然后,通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封装”。面向对象的程序设计具有“抽象”,“封装”“继承”“多态” 四个基本特点。
之前python面向对象方法中我们已经了解了类的定义以及类的成员变量和类的成员函数,C++中相关概念与其类似,这里不再重复介绍。我们看看C++中如何使用类的成员变量和成员函数。同样,我们可以使用对象名.成员名实现,此外,不同于python的是C++还可以通过指针->成员名以及引用名.成员名实现。例如:
class CRectangle{ public: int w, h; int Area() { return w * h; } int Perimeter(){ return 2 * ( w + h); } void Init( int w_,int h_ ) { w = w_; h = h_; }}; //必须有分号CRectangle r1, r2;CRectangle * p1 = & r1;CRectangle & rr = r2;p1->w = 5;rr.w = 5;rr.Init(5,4); // rr 的值变了,r2的值也变
类成员的可访问范围
在类的定义中,用下列访问范围关键字来说明类成员可被访问的范围,在类的定义中这三种关键字出现的次数和先后次序都没有限制。
– private: 私有成员,只能在成员函数内访问
– public : 公有成员,可以在任何地方访问
– protected: 保护成员
如过某个成员前面没有上述关键字,则缺省地被认为是私有成员。在类的成员函数内部,能够访问当前对象的全部属性、函数以及同类其它对象的全部属性、函数。但在类的成员函数以外的地方,只能够访问该类对象的公有成员。
设置私有成员的机制,叫“隐藏”。“隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
同样,我们也可以利用C语言中学到的struct定义类,struct和用"class"的唯一区别,就是未说明是公有还是私有的成员,默认公有。
一什么是引用?某个变量的引用,等价于这个变量,相当于该变量的一个别名。下面我们定义一个引用,并将其初始化为引用某个变量。类型名 & 引用名 = 某变量名; 除了可以在定义时候使用引用之外,引用还可以作为函数的返回值 。
注意: 定义引用时一定要将其初始化成引用某个变量。初始化后,它就一直引用该变量,不会再引用别的变量了。同时,引用只能引用变量,不能引用常量和表达式。例如:
double a = 4, b = 5;double & r1 = a;double & r2 = r1; // r2也引用 ar2 = 10;cout << a << endl; // 输出 10r1 = b; // 注意r1并没有引用b,只是相当于赋值操作cout << a << endl; //输出 5
之前我们在文章 指针的用法你真的搞懂了吗?中介绍了c语言中如何通过指针来交换两个数据的值,那么在C++中如何更加简单方便的实现数值的交换呢?答案就是利用引用实现。
void swap( int & a, int & b){int tmp;tmp = a; a = b; b = tmp;}int n1, n2;swap(n1,n2) ; // n1,n2的值被交换
常引用
当我们在定义引用时,前面加const关键字,即为“常引用”。不能通过常引用去修改其引用的内容,这样能够避免我们在编程时在函数内部不小心改变参数的内容。同时,我们需要注意 const T & 和T & 是不同的类型!!! T & 类型的引用或T类型的变量可以用来初始化const T & 类型的引用。const T 类型的常变量和const T & 类型的引用则不能用来初始化T &类型的引用,除非进行强制类型转换。
“const”关键字的用法1. 定义常量:不能通过常引用修改其引用的变量。
const int MAX_VAL = 23;MAX_VAL = 5; //error
2. 定义常量指针: 不可通过常量指针修改其指向的内容,但可以改变常量指针的指向。同时,我们不能把常量指针赋值给非常量指针,反过来可以。
int n,m;const int * p = & n; int * p2;* p = 5; // 编译出错n = 4; //okp = &m; //ok,p = p2; //okp2 = p; //error
3. 常量成员函数:在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。常量成员函数内部不能改变属性的值,也不能调用非常量成员函数。例如:(注意与下面一个示例进行对比)
class Sample { private : int value; public: void func() { }; Sample() { } void SetValue() const { value = 0; // wrong,不能改变属性的值 func(); //wrong,不能调用非常量成员函数 }};const Sample Obj;// 常量对象Obj.SetValue (); // 常量对象上可以使用常量成员函数
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加 const关键字。同时注意当我们定义为常量对象之后,常量对象只能使用构造函数、析构函数和有const 说明的函数( 常量方法)。
class Sample {private : int value;public: Sample() { } void SetValue() { }};const Sample Obj; // 常量对象Obj.SetValue (); //错误
我们在定义常量成员函数和声明常量成员函数时都应该使用const 关键字。
class Sample { private : int value; public: void PrintValue() const; }; void Sample::PrintValue() const { //此处不使用const会导致编译出错 cout << value;}void Print(const Sample & o) { o.PrintValue(); //若 PrintValue非const则编译错}
那么如果我们想要修改const成员函数中成员变量的值该如何实现呢?C++提供了mutable成员变量来实现这一功能,mutable成员变量可以在const成员函数中修改的成员变量。
class CTest{public: bool GetData() const{ m_n1++; return m_b2; }private: mutable int m_n1; bool m_b2;};
如果一个成员函数中没有调用非常量成员函数,也没有修改成员变量的值,那么,最好将其写成常量成员函数。
二动态内存分配 new 运算符c语言中我们通过void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。而在C++中我们用new 运算符实现动态内存分配 。可以通过new 运算符分配一个变量 P = new T; ,也可以分配一个数组P = new T[N]; 。动态分配出一片大小为 sizeof(T)或sizeof(T) *N 字节的内存空间,并且将该内存空间的起始地址赋值给P。并且这里的P是T *类型的指针。例如:
int * pn,*p; int i = 5;pn = new int;* pn = 5;p = new int[i * 20];p[0] = 20;
delete 运算符
C++中用“new”动态分配的内存空间,一定要用“delete”运算符进行释放。通过new申请的同一片内存空间只能delete 一次。释放动态分配的变量通过delete 指针;实现,该指针必须指向new出来的空间。用“delete”释放动态分配的数组,要加“[]”,表示为 delete [ ] 指针;。
int * p = new int[20];p[0] = 1;delete [ ] p;
三其他重要的概念内联函数
函数调用是有时间开销的。如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
内联函数的实现是在函数前面加关键字inline。例如:
inline int Max(int a,int b){ if( a > b) return a; return b;}
函数重载
函数重载我们在介绍python面向对象方法的时候也介绍到方法重写类似,可以查看 Python面向对象方法。一个或多个函数,名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。函数重载使得函数命名变得简单,编译器根据调用语句的中的实参的个数和类型判断应该调用哪个函数(多态的思想)。但是在使用的时候一定要避免二义性,即编译器不知道该调用哪个函数。例如:
int Max(double f1,double f2) { }int Max(int n1,int n2) { }int Max(int n1,int n2,int n3) { }Max(3.4,2.5); //用 调用 (1)Max(2,4); //用 调用 (2)Max(1,2,3); //用 调用 (3)Max(3,2.4); //error,二义性,不知道该调用(1)还是(2)
函数缺省参数
在C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。函数参数可缺省的目的在于提高程序的可扩充性。即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。例如:
void func( int x1, int x2 = 2, int x3 = 3){ }func(10 ) ; //于 等效于 func(10,2,3)func(10,8) ; //于 等效于 func(10,8,3)func(10, , 8) ; // 不行, 只能最右边的连续若干个参数缺省
我们刚刚介绍了常量成员函数,那么对于常量成员函数的重载又是怎么回事呢?两个函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
#include using namespace std;class CTest { private : int n; public: CTest() { n = 1 ; } int GetValue() const { return n ; } int GetValue() { return 2 * n ; } };
静态成员
设置静态成员是为了将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
静态成员是在定义前面加了static关键字的成员。普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。因此sizeof 运算符在计算内存空间时不会计算静态成员变量。例如:
class CMyclass {int n;static int s;};sizeof( CMyclass ) // 等于 4
静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。同时,需要注意的一点是普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。因此静态成员不需要通过对象就能访问。那么我们该如何访问静态成员函数呢?例如,给定一个长方形类的定义,对其中静态变量或函数的访问可以用以下四种方式实现:
class CRectangle{ private: int w, h; static int nTotalArea; // 静态成员变量 static int nTotalNumber; public: static void PrintTotal(); // 静态成员函数};
1) 类名::成员名
CRectangle::PrintTotal();
2) 对象名.成员名
CRectangle r; r.PrintTotal();
3) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
4) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
注意事项:
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。例如:
void CRectangle::PrintTotal(){ cout << w << "," << nTotalNumber << "," <endl; }CRetangle::PrintTotal(); //解释不通,w 到底是属于那个对象的?
end
点击“❀在看”,让更多朋友们看到吧~