目录
7、关于const int * ,int const * 以及 int * const 的区别
1、内置函数
调用函数时需要一定的时间和空间的开销。函数调用过程如下:
1.程序先执行函数调用之前的语句;
2.调用函数,流程控制转移到被调用函数的入口处,同时进行参数传递;
3.执行被调用函数中函数体的语句;
4.流程返回调用函数的下一条指令处,将函数返回值带回;
5.接着执行主调函数中未执行的语句。
这样就要求在转到被调用函数之前,需要动态分配内存(动态存储区),记下当时执行的指令地址,还要“保护现场”(记下当时有关信息),以便在函数调用之后继续执行。函数调用之后,流程返回到当前记下的地址,并且根据记下的信息进行“恢复现场”,然后继续执行,这都需要花费一定时间。如果有的函数需要频繁调用,则所用时间会很长,从而降低程序执行效率。有些程序对效率是有要求的,要求系统的响应时间短,这就希望尽量压缩时间的开销。
C++提供一种提高效率的方法,即在编译时将所调用函数的代码直接嵌入到主调函数中,而不是将流程转出去。这种嵌入到主调函数中的函数称为内置函数(内联函数)。
定义方法:只需在函数左边加一个关键字inline即可。
2、重载函数
重载声明
让函数名字跟描述程序行为的名字保持一致是一个良好的编程习惯。每种负责输出的函数最好是都叫做put。不幸的是,C不允许程序中有同名的函数。
C中独一无二函数名的限定对函数库的使用者和作者都是一种负担。作者需要想象出相近但差异又不能太大的函数名,而使用者需要学会这些不同。一个认真的作者会浪费数小时来设计一组函数名前缀或后缀,以便减少使用者的负担。
C++通过重载函数名来避免这类麻烦。你可以在同一个程序里使用同名的两个或多个函数。函数名重载可以让函数使用起来更“自然”。使用了重载的程序也更容易读和写。(当然,过犹不及,过多的重载也并非好事)
C++中声明重载的函数跟声明其它函数没什么不同。只不过它跟其它的某个函数重名。重载函数必须使用不同的变量,否则编译器没法区分它们。
例如,可以定义如下的重载函数put:
int put(int c); // putchar
int put(int c, FILE *f); // fputc
int put(char const *s); // puts
int put(char const *s, FILE *f); // fputs
重载决策
当编译器遇到对函数put的调用,它会选择函数参数完全匹配的函数声明进行调用。这个过程叫做重载决策。例如:
put('a', stdout);
调用如下的函数声明:
int put(int c, FILE *f);
而:
put("Hello\n");
调用:
int put(char const *s);
如果编译器没法找到参数匹配的函数,会报错。例如,调用:
put("Error #", n, stderr);
会报错,因为没有函数声明了三个变量。
尽管调用的函数的参数个数必须跟匹配的声明函数一致,但参数类型却不必完全一致。
C++允许进行一些参数的隐式类型转换。
3、函数模板
c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
注意:模板定义本身不参与编译,而是编译器根据模板的用户使用模板时提供的类型参数生成代码,再进行编译,这一过程被称为模板实例化。用户提供不同的类型参数,就会实例化出不同的代码。
普通函数模板
template<class T>
void Swap(T & x, T & y)
{
T tmp = x;
x = y;
y = tmp;
}
成员函数模板
class Printer {
public:
template<typename T>
void print(const T& t) {
cout << t <<endl;
}
};
Printer p;
p.print<const char*>("abc"); //打印abc
类模板
#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Pair
{
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k,T2 v):key(k),value(v) { };
bool operator < (const Pair<T1,T2> & p) const;
};
int main()
{
Pair<string,int> student("Tom",19); //实例化出一个类 Pair<string,int>
cout << student.key << " " << student.value;
return 0;
}
4、变量的储存
静态存储,动态存储
静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。
动态存储变量是在程序执行过程中,使用它时才分配存储单元, 使用完毕立即释放。
在C语言中,有以下四种变量:静态变量(static)、自动变量(auto)、寄存器变量(register)、外部变量(extern)。
其中自动变量和寄存器变量属于动态存储方式,外部变量和静态局部变量属于静态存储方式。
静态变量
局部静态(static)变量,作用域为局部,而生命周期是全程。 静态局部变量属于静态存储方式,它具有以下特点: (1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。 (2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
自动变量
存储特点:
自动变量属于动态存储方式。
在函数或复合语句中定义的自动变量,只在该函数复合语句内有效;函数复合语句被调用时分配存储空间,调用结束就释放。
初始化:
定义而不初始化,则其值是不确定的。
如果初始化,则赋初值操作是在调用时进行的,且每次调用都要重新赋一次初值。
由于自动变量的作用域和生存期,都局限于定义它的个体内(函数或复合语句),
因此不同的个体中允许使用同名的变量而不会混淆。
即使在函数内定义的自动变量,也可与该函数内部的复合语句中定义的自动变量同名。
寄存器变量
存储特点:
动态储存
一般情况下,变量的值都是存储在内存中的。
为提高执行效率,C语言允许将局部变量的值存放到寄存器中,这种变量就称为寄存器变量。
寄存器变量需要注意以下几点:
1)只有局部变量才能定义成寄存器变量,即全局变量不行。
2)对寄存器变量的实际处理,随系统而异。例如,微机上的MSC和TC 将寄存器变量实际当作自动变量处理。
3)允许使用的寄存器数目是有限的,不能定义任意多个寄存器变量。
外部变量
存储特点:
外部变量属于静态存储方式。又分为静态外部变量和非静态外部变量
静态外部变量
只允许被本源文件中的函数引用
定义格式: static 数据类型 外部变量名;
非静态外部变量
允许被其它源文件中的函数引用
定义时缺省static关键字的外部变量,即为非静态外部变量。
定义格式:数据类型 外部变量名;
其它源文件中的函数,引用非静态外部变量时,需要在引用函数所在的源文件中进行说明:
格式:extern 数据类型 外部变量表;
静态局部变量和静态外部变量同属静态存储方式,但两者区别较大:
1)定义的位置不同。
静态局部变量在函数内定义,静态外部变量在函数外定义。
2)作用域不同。
静态局部变量属于内部变量,其作用域仅限于定义它的函数内;
虽然生存期为整个源程序,但其它函数是不能使用它的。
静态外部变量在函数外定义,其作用域为定义它的源文件内;
生存期为整个源程序,但其它源文件中的函数也是不能使用它的。
3)初始化处理不同。
静态局部变量,仅在第1次调用它所在的函数时被初始化,
当再次调用定义它的函数时,不再初始化,而是保留上1次调用结束时的值。
而静态外部变量是在函数外定义的,不存在静态内部变量的“重复”初始化问题,
其当前值由最近1次给它赋值的操作决定。
把局部变量改变为静态内部变量后,改变了它的存储方式,即改变了它的生存期。
把外部变量改变为静态外部变量后,改变了它的作用域,限制了它的使用范围。
因此,关键字“static”在不同的地方所起的作用是不同的。
总结即为:
外部连接性:变量允许外部文件访问,定义为全局变量,其它文件通过 extern 声明之后就可以进行访问。全局变量定义的时候应在函数外声明,并且不能添加 static关键字。
内部连接性:只允许本地文件访问,其它文件不能通过extern 声明访问,和全局变量一样也是在函数外定义,但是要在变量类型前加上 static 关键字,这也是我们常说的 static隐藏变量的功能。
无连接性 :只允许当前代码段访问。也就是说在声明的时候是在函数内部声明,只能通过调用函数来访问的静态变量, 定义在函数内部,类型前加static关键字。
同时普通函数添加static后也会导致仅本文件可见。
从链接性总结, static关键字起到了隐藏的作用。同时静态变量(上述三种)都会进行零初始化。将变量初始默认初始化为0
5、内存泄漏
内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
其中分为堆内存泄漏(Heap leak)和系统资源泄露(Resource Leak)。Heap leak对内存指的是程序执行中依据须要分配通过malloc,realloc new等从堆中分配的一块内存,再是完毕后必须通过调用相应的 free或者delete 删掉。假设程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
要有良好的编码习惯,尽量在涉及内存的程序段,检測出内存泄露。当程式稳定之后,在来检測内存泄露时,无疑添加了排除的困难和复杂度。使用了内存分配的函数,要记得要使用其想用的函数释放掉,一旦使用完成。
Heap memory:
malloc\realloc ------ free
new \new[] ---------- delete \delete[]
GlobalAlloc------------GlobalFree
要特别注意数组对象的内存泄漏
MyPointEX *pointArray =new MyPointEX [100];
其删除形式为:delete []pointArray
6、THIS指针
#include<iostream.h>
class Point
{
int x, y;
public:
Point(int a, int b) { x=a; y=b;}
Void MovePoint( int a, int b){ x+=a; y+=b;}
Void print(){ cout<<"x="<<x<<"y="<<y<<endl;}
};
void main( )
{
Point point1( 10,10);
point1.MovePoint(2,2);
point1.print( );
}
当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。
MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:
void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}
即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。
即该函数过程可写成 point1.x+= a; point1. y + = b;
关于this的经典回答:
当你进入一个房子后,
你可以看见桌子、椅子、地板等,
但是房子你是看不到全貌了。
对于一个类的实例来说,
你可以看到它的成员函数、成员变量,
但是实例本身呢?
this是一个指针,它时时刻刻指向你这个实例本身。
7、关于const int * ,int const * 以及 int * const 的区别
https://blog.csdn.net/qq_37941471/article/details/80678904
个人观点:
1、const int* a 和 int const * a 等同
首先要清楚,a是什么。a是指针,前面不管是const int,亦或是int const,只不过是修饰指针的而已,所以a本质上来说还是一个指针。既然是指针,就要知道它指向谁。明显,它指向一个常整形,那就说明,const修饰的int,而不是指针,也就说明一下代码是合理的,即指针指向的对象是可以改变的
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int j = 20;
int const * p = &i;
p = &j;
j = 30;
printf(" p = %d \n", *p);
system("pause");
return 0;
}
当然也可以改变指针所指对象的数值,已达到输出*p时的结果发生改变
2、int * const a 此时const 修饰 p ,也就是指针,说明这个指针是常量,既然是常量,那就要初始化指针且在以后的程序中不可再次出现修改此指针的代码出现。
8、静态成员变量和静态成员函数
http://c.biancheng.net/view/165.html
个人观点:
1、静态成员变量只有一份,被所有同类对象共享。
静态变量的特点就是在整个生命周期他都会存在,是在静态储存。因此它并不属于某个类的实例,而是属于整个类,它不是为类的实例去服务的,而是服务于整个类。这样的话也好理解为什么它要在类外定义,因为它只能定义一次。
2、静态成员函数并不具体作用在某个对象上。同样它也是为类服务的,这样说明了为什么静态成员函数中不能有非静态成员变量,因为它不知道此非静态成员变量是属于哪个类的实例的。同理静态成员函数不能调用非静态成员函数。
9、常成员函数、常成员变量、常对象
http://c.biancheng.net/view/2230.html
http://c.biancheng.net/view/2232.html
个人观点:
1、常对象(类的常实例)以及常对象指针,它们都只能调用 const 成员函数。
2、必须在成员函数的声明和定义处同时加上 const 关键字。
int getage() const;
int Student::getage() const{
return m_age;
}
10、友元函数
在 C++ 中,一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。现在,我们来介绍一种例外情况——友元(friend)。借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
http://c.biancheng.net/view/2233.html
个人观点:
1、当友元函数的目的是可以获取到本类的私有函数时,与普通的公有成员函数相似,但需要注意的是,友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象。
//m_name、m_age、m_score都是类Student私有变量
//错误
void show(){
cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
}
//正确
void show(Student *pstu){
cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
}
2、其他类的成员函数声明为友元函数,这样一个函数可以被多个类声明为友元函数,这样就可以访问多个类中的 private 成员。
11、继承与派生
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。
12、虚函数
http://c.biancheng.net/view/2294.html
个人观点:
1、通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。
2、有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。
3、C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。