C 和 C++ 基础
extern 关键字作用
extern 可用置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外 extern 也可以用来进行链接指定。
简单来说 extern 有两个作用:
1.链接指定,当 extern 与 “C” 一起连用时,是告诉编译器在编译函数时,按照C的规则去编译相应的函数名,而不是按照C++的规则。因为C++支持函数重载,会使得函数名“面目全非”,在某些场合下,这会影响到链接器的工作。
有以下两种方式来规定函数的编译规则:
extern "C" void func1();
extern "C"
{
void func2();
}
有 “C” 相应的就有 “C++”。但一般没地方会用到这个,因为只有 C++ 的编译器会支持这种语法
。
extern "C++" void func1();
extern "C++"
{
void func2();
}
当编程员自己也不确定使用的编译器是C的还是C++时,采用 __cplusplus 宏可以处理这种情况。
#ifdef __cplusplus
extern "C"
{
#endif
// 代码...
#ifdef __cplusplus
}
#endif
2.修饰变量,告知编译器这个变量是在其他地方声明或定义过的全局变量,在链接时再去寻找该变量。
这里再简单讲述一下变量存储类型,以便于了解 extern。
关键字 | 变量存储类型 |
---|---|
auto | 自动变量:在函数中的所有非静态局部变量。变量存储在动态存储区(堆或栈)中。 |
static | 静态变量:包括全局静态变量和局部静态变量。变量存储在静态存储区。 |
extern | 外部变量: 全局变量。变量存储在静态存储区。 |
register | 寄存器变量:变量存储在寄存器中,读写快。 |
C++11已经弃用 register,使用多的变量会被编译器自动优化存放位置,而auto被赋予了自动类型推导的功能。
static 关键字作用
1.修饰局部变量,延长变量的生命周期。
2.修饰全局变量,隐藏该全局变量,使得该变量只能被本文件所访问。
3.修饰函数,隐藏该函数,使得该函数只能被本文件所调用。
4.修饰类成员数据,使得该成员数据被整个类共享,而不只属于某个单一对象。
volatile 关键字作用
volatile 有三大特性,如下:
1.易变形。在汇编层面上,如果有访问同一 volatile 变量的两条语句,第二条语句不会直接使用上一条语句对应的 volatile 变量的寄存器内容,而是直接从内存中读取。
2.不可优化性,字面意思,编译器不会对 volatile 变量进行过激的优化。
3.顺序性,C/C++ Volatile关键词前面提到的两个特性,让Volatile经常被解读为一个为多线程而生的关键词:一个全局变量,会被多线程同时访问/修改,那么线程内部,就不能假设此变量的不变性,并且基于此假设,来做一些程序设计。当然,这样的假设,本身并没有什么问题,多线程编程,并发访问/修改的全局变量,通常都会建议加上Volatile关键词修饰,来防止C/C++编译器进行不必要的优化。
volatile用在如下的几个地方:
1.中断服务程序中修改的供其它程序检测的变量需要加volatile。
2.多任务环境下各任务间共享的标志应该加volatile。
3.存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义。
const 关键字作用
1.修饰指针变量
const char* p; // 指针指向的值不能改变
char* const p; // 指针的指向不能改变(p 本身存放得地址不能改变)
2.修饰成员变量和成员函数
class A
{
private:
const int a; // 此成员变量不能被修改
public:
const int func1(int a, int b); // 返回值不能改变(返回值不能做左值)
int func2(const int a, const int b); // 函数参数不能被改变
int func3(int a, int b) const; // 类成员不能被此函数修改
};
new 与 malloc 的区别
/*
malloc和free是库函数,new和delete是C++操作符
new自己计算需要的空间大小和返回的指针类型;malloc需要指定大小,返回 void*
new在动态分配内存的时候初始化对象,调用其构造函数,delete调用析构函数;malloc只是返回指向内存的指针
new和delete可以进行操作符重载
*/
class A
{
};
int main()
{
A* a = new A;
A* b = (A*)malloc(sizeof(A)); // 注意此时,sizeof(A) = 1
A* c = new A[10];
A* d = (A*)malloc(sizeof(A) * 10);
}
C++的多态性和虚函数表
多态性(polymorphism),简单地来说是“一个接口,多种方法”。
C++的多态性是通过重写(override)和重载(overload)实现的。
重写通过虚函数实现,虚函数允许子类重新定义成员函数。
重载主要通过编译器实现,编译会为同名函数(参数或返回值不同)重新命名以便在链接时能够区分出函数的不同。
多态的目的是为了接口重用,无论是什么对象,都能通过同一接口找到合适的实现方法。
// overload
int func(int a)
{
return a;
}
int func(int a, int b)
{
return a + b;
}
// override
class A
{
public:
void func1()
{
cout << 1 << endl;
}
virtual void func2()
{
cout << 2 << endl;
}
virtual ~A(){}
};
class B : public A
{
public:
void func1()
{
cout << 3 << endl;
}
void func2()
{
cout << 4 << endl;
}
};
int main()
{
A a;
B b;
A* p;
p = &a;
p->func1();
p->func2();
p = &b;
p->func1();
p->func2();
cout << func(1) << endl;
cout << func(1, 1) << endl;
return 0;
}
结果应当为:
1
2
1
4
1
2
纯虚函数
定义:
纯虚函数是在其基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。
// 有纯虚函数的类称之为纯虚类(抽象类)
// 纯虚类不能实例化
class A
{
public:
virtual void print() = 0;
virtual ~A(){}
};
class B : public A
{
public:
// 子类必须完成父类纯虚函数的实现方法
void print()
{
cout << 1 << endl;
}
~B(){}
};
虚函数表
虚函数是通过虚函数表来实现的。
虚函数表其实是一张包含各种虚函数地址(虚函数指针)的表。C++编译器保证虚函数表的指针存在于对象实例中最前面的位置。
虚函数按照其声明顺序放于表中,父类的虚函数在子类的虚函数前面。如果子类有重写父类的虚函数,会将相应的虚函数覆盖(父类子类的各自维护自己的虚函数表)。
每个类会维护一张虚函数表,每个对象会有指向虚函数表的指针。
#include <iostream>
using namespace std;
class A
{
public:
virtual void print()
{
cout << "A: print()" << endl;
}
protected:
virtual void test()
{
cout << "A: test()" << endl;
}
};
class B : public A
{
public:
void print()
{
cout << "B: print()" << endl;
}
};
typedef void (* pFunc) ();
void AAA()
{
cout << "AAA()" << endl;
}
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
A* a = new A;
B* b = new B;
pFunc pfunc1 = NULL, pfunc2 = NULL, pfunc3 = NULL;
/*
A a
(int*)(&a) 虚函数表的指针
*((int*)(&a)) 指向虚函数表的第一项(虚函数表的开始地址)
(int*)*((int*)(&a)) 把虚函数表的第一项视作指针
*((int*)*((int*)(&a))) 虚函数表的第一个指针指向的东西(虚函数)
*/
pfunc1 = (pFunc)*((int*)*((int*)(a)));
pfunc2 = (pFunc)*((int*)*((int*)(b)));
// 64位环境下,指针的长度为8字节,2个int类型的长度
pfunc3 = (pFunc)*((int*)*((int*)(b)) + 2);
pfunc1();
pfunc2();
pfunc3();
delete a;
delete b;
a = NULL;
b = NULL;
return 0;
}
输出结果如下(64位环境):
8
8
A: print()
B: print()
A: test()