头文件包含结构声明和使用这些结构的函数的原型(声明函数)
实际上,我们如果要自己写一个头文件,那么头文件里面最好不要有函数的定义,和变量的声明,否则会产生错误。而正确的做法是,在头文件里面包含函数的声明,而函数的定义放在另一个cpp文件里面。那么就简单的知道了,一个头文件和一个实现头文件的.cpp文件是配套使用的。头文件不定义函数,只声明函数
那么如何创建头文件呢?
很简单,下面是我自己写的。
然后建立一个一样名字的cpp文件来定义头文件声明的函数
最后用一个源程序测试
上面可以说是很简单易懂,先简单了解一下,下面会因此展开一些问题。
为了避免同一个文件被include多次,C/C++中有两种方式,一种是#ifndef方式,一种是#pragma once方式。在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别。
上面已经提到,建立头文件的时候,在文件第一行要写上 #pragma once 这条语句,那么实际上,还可以这样写
#ifndef MYSELF_H_
#define MYSELF_H_
....
#endif
那么看到这些确实很疑惑。下面来解读一下这些语句
第一句话#ifndef MYSELF_H_的意思是如果这个标识符被定义了,那么就不再定义,直接跳到#endif后面一句。如果没有定义,那么走到下一行,定义该标识符,然后,编译器将查看#ifndef和#endif之间的内容.
这种方法不能防止编译器将文件包含两次,而只是让它忽略除第一次包含之外的全部内容。
至于区别是什么,暂时还不知道,先挂在这里
有些被称为存储说明符或cv-限定符的C++关键字提供了其它有关存储的信息,下面是存储说明符;
上面两个关键字就是cv限定符。
首先,我们对const很熟悉,无论是对常规变量的初始化,还是对常量指针,指针常量这些乱七八糟的对象,都会用到const. 那么,volatile是什么呢?
volatile的本意是“易变的”,volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
mutable指出,即使结构(或类)变量为const,其某个成员也可以被修改。例如:
struct data
{
char name[30];
mutable int accesses;
};
const date veep={"lby",12};
strcpy(veep.name,"ybl"); //不允许,因为const限定符
veep.accesses++; //允许,因为mutable解除const的限制
(1)在C++中,const限定符对默认存储类型稍微有些影响。在默认情况下全局变量的链接性是外部的,但是const的全局变量的链接性是内部的。也就是说,在C++看来,全局const定义就像使用了static一样。
(2)将一组常量放在头文件中,由于const常量是的链接性是内部的,所以每个文件相当于有一组自己私有的常量,互不相干。
(3)在函数和代码块中声明const时,其作用域为代码块,即仅当程序执行该代码块中的代码时,该常量才是可用的。这意味着在函数或代码块中创建常量时,不必担心其名称与其它地方定义的常量发生冲突。
(4)如果需要使用该常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性: extern const int state=50;
(1)函数也有链接性,在默认情况下,函数的链接性是外部的,也就是说,一个文件里面的函数可以在其它文件里面共享。
(2)函数的存储持续性都是自动为静态的,即整个程序运行期间一直存在。
(3)可以使用static关键字将函数的链接性设置为内部,使之只能在一个文件中使用。但是必须同时在原型和函数定义中使用该关键字。
static int private(double x); //函数声明
...
static int private(double x) //函数定义
{
....
}
这样的话,该函数就只在该文件可用,其它文件不共享。其它文件当然可以再次使用同名定义函数。和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍将使用静态函数.
(4)内联函数不受单定义规则制约。这允许程序员能够将内联函数的定义放在头文件中。这样,包含了头文件的每个文件都有内联函数的定义。然而,C++要求同一个函数的所有内联定义都相同。
(5)每个外部函数只能有一个定义,如果有两个,则会报错。
(6)C++在哪里查找函数
如果文件中的函数原型指出该函数时静态的,则编译器将只在该文件中查找函数定义。否则将在所有的程序文件中查找。如果在所有的程序文件中没有找到,编译器将在库中查找。这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数。
在c语言中,一个名称对应一个函数,为满足内部需要,c语言编译器可能将spiff这样的函数名翻译为_spiif。这种方式被称为C语言链接性。但在C++中,同一个名称可能对应多个函数(函数重载等),那么对于编译器来说,就必须将这些相同函数名翻译成不同的符号名称,所以C++编译器将执行名称矫正或名称修饰。这叫做C++语言链接。。例如,可能将spiff(int)转换为_spiff_i,将spiff(double,double)转换为_spiff_d_d.
那么提到了C和C++对于语言链接有着不同的处理方案,即“翻译名称”不一样,那么当在C++程序中使用C库中预编译的函数,将会出现什么情况呢?例如如下代码:
spiff(22);
在C库中,它的符号名称为_spiff,但对于我们假设的链接程序来,C++查询约定时查找符号名称_spiff_i。这怎么办呢?
答案是:指出约定。
extern "C" void spiff(int); //显式使用C
extern void spoff(int); //默认使用C++,因为是在C++程序中,所以默认的是C++
extern "C++" void spaff(int); //显式使用C++
(1)new的初始化
int *pi=new int(8); //意思是创建一个int类型的变量,它的值为8,pi指向这个值,pi是这个值的地址
如果需要初始化结构体,那么就需要用大括号,但是这要求编译器支持C++11.
struct where{double x;double y;double z;};
where * one=new where{3.3,123.3, 9.9 };
int *a=new int [4] {1, 2, 3, 4};
(2)new分配内存失败
早10年,C++让new返回空指针,单现在将引发异常std ;bad_alloc。
(3)定位new运算符
通俗来说,定位new运算符就是在指定位置开辟一个空间,然后使用这个空间,这就是“定位”。不过,这个指定的空间不是重新去堆新开辟,而是在已有数组中借走一部分。
#include
#include
using namespace std;
struct chaff
{
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[100];
int main()
{
chaff* p1, * p2;
int* p3, * p4;
p1 = new chaff;
p3 = new int[20];
p2 = new(buffer1) chaff; //从buffer1数组中借走一个chaff的长度,观察到是从buffer1[0]开始借走的
p4 = new(buffer2)int[20]; //从buffer2数组借走80个字节
//下面的赋值是观察借走的是哪一部分
*p2 = {"boy",3};
for (int i = 0; i < 20; i++)
*(p4 + i) = i + 1;
cin.get();
return 0;
}
那么上面的程序可以知道,重定位new运算符不是在堆里面开辟空间,而是重复利用已经有的数组空间。再一个,如果不设置偏移量,那么每次这样:new(buffur1)chaff,都是在buffer1数组头借用空间,new不会去检查这个空间是否被用过,那么如果想要借走buffer1数组其它位置的空间,就需要设置偏移量,比如new(buffer1+5*sizeof(int))chaff,那么借走的空间将是buffer[4]-buffer[28]。
定位new运算符的工作原理:返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型。
在C++中,随着项目的增大,可能会产生这样一个问题:可能会有“变量名”(这里的变量名指所有需要手动输入的名称)重复,导致名称冲突。
C++标准提供了名称空间工具,以便更好地控制名称的作用域。
传统的C++名称空间
首先知道两个术语:
声明区域是指一个变量或函数可以在其中声明的区域。潜在作用域是指声明点到声明区域结束的范围内。但变量的可见性会因为其潜在作用域中是否存着同名覆盖的局部变量而影响。
新的C++名称空间
通过定义一种新的声明区域来创建命名的名称区域,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中的东西。
下面介绍一个术语:作用域解析运算符::
作用域解析运算符的作用是用来访问名称空间中的名称的。
首先,先创建名称空间Boy,Girl。
namespace Boy
{
char name[100];
int sex;
int age;
void act(); //这里先写原型,可以在别的文件再去定义
}
namespace Girl
{
char name[100];
int sex;
int age;
}
如果要使用变量sex,name,age,则可以使用作用域解析运算符来区分。
Boy::age=10;
Girl::age=11;
Boy::act(); //可以有函数哦,但是注意,函数的定义一定要写在名称空间里面。
总结:绕来绕去,简而言之,就是在内存分配空间分别存放各自需要的变量,这些变量可以一样,可以不一样,但是前提是加上::用来区分。
using 声using 编译指令
C++提供两种机制来使用便捷名称空间。
(1)using 声明
using声明由被限定的名称和它前面的关键字using组成:
例如:using Boy::age;
using声明将特定的名称添加到它所属的声明区域。
例如在main里面:
int main()
{
using Boy::age; //将Boy里面的age的作用域放置到main函数里面,而且将会覆盖全局变量。
age=10; //等价Boy::age=10;
}
(2)using编译指令
using声明使一个名称可用,而using编译指令使所有的名称都可用。using编译指令由名称空间名和它前面的关键字using namespace 组成,它使名称空间中的所有名称都可用,而不需要作用域解析运算符::
#include
using namespace std; //这里的作用域使全局
int main()
{
using namespace Boy; //这里的作用域是main函数
}