单独编译
C++允许甚至鼓励将组件函数放在独立的文件中。可以单独编译这些文件,然后链接成可执行的程序。 如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。
头文件中常包含的内容:
- 函数原型
- 使用#define 或 const 定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
//coordin.h -- structure templates and function prototypes
//structure templates
#ifndef COORDIN_H_
#define COORDIN_H_
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif // !COORDIN_H_
//file1.cpp -- example of a three-file program
#include <iostream>
#include "coordin.h"
using namespace std;
int main() {
rect rplace;
polar pplace;
cout << "Enter the x and y values:";
while (cin >> rplace.x >> rplace.y){
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Bye!\n";
return 0;
}
// file2.cpp -- contains functions called in file1.cpp
#include <iostream>
#include <cmath>
#include "coordin.h"
polar rect_to_polar(rect xypos) {
using namespace std;
polar answer;
answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.y, xypos.x);
return answer;
}
//show polar coordinates, converting angle to degrees
void show_polar(polar dapos) {
using namespace std;
const double Rad_to_deg = 57.29577951;
cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle * Rad_to_deg;
cout << " degrees\n";
}
寄存器变量
关键字register最初是由C语言引入的,他建议编译器使用CPU寄存器来存储自动变量:
register int count_fast;// request for a register variable
单定义规则
该规则指出,变量只能有一次定义。为满足这种需求,C++提供了两种变量声明,一种是定义生命(defining declaration)或简称为定义(definition),它给变量分配存储空间;另一种是引用声明(referencing declaration)或简称为声明(declaration),他不给变量分配存储空间。
如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他所有文件中,都必须使用关键字extern声明它:
//file01.cpp
extern int cats = 20; // definition because of initialization
int dogs = 22; // also a definition
int fleas; // also a definition
...
//file02.cpp
//use cats and dogs from file01.cpp
extern int cats;
extern int dogs;
...
//file98.cpp
// use cats, dogs, and fleas from file01.cpp
extern int cats;
extern int dogs;
extern int fleas;
...
单定义规则并非意味着不能有多个变量的名称相同。在不同函数中声明的同名自动变量是彼此独立的,它们都有自己的地址。
C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。为了将程序分为许多文件,则需要在文件中共享代码,例如一个文件的代码可能需要另一个文件中中定义的变量。
为了支持分离式编译,C++允许将声明和定义分离开来。变量的声明规定了变量的类型和名字,即使一个名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。定义则负责创建与名字关联的实体,定义还申请存储空间。
如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显式地初始化变量:
extern int i; //声明i而非定义
int j; //声明并定义i
但我们也可以给由extern关键字标记的变量赋一个初始值,但这样就不是一个声明了,而是一个定义:
extern int v = 2;
int v = 2; //这两个语句效果完全一样,都是v的定义
注意: 变量能且只能被定义一次,但是可以被声明多次。
//external.cpp -- external variables
//compile with support.cpp
#include <iostream>
using namespace std;
//external variable
double warming = 0.3;
void update(double dt);
void local();
int main() {
cout << "Global warming is " << warming << " degrees.\n";
update(0.1);
cout << "Global warming is " << warming << " degrees.\n";
local();
cout << "Global warming is " << warming << " degrees.\n";
return 0;
}
//support.cpp -- external variables
//compile with external.cpp
#include <iostream>
using namespace std;
//external variable
extern double warming; // use warming from another file
void update(double dt);
void local();
void update(double dt) {
extern double warming;
warming += dt;
cout << "Updating global warming to " << warming;
cout << " degrees.\n";
}
void local() {
double warming = 0.8;
cout << "Local warming = " << warming << " degrees.\n ";
cout << "But global warming = " << ::warming << " degrees.\n ";
}
另外,函数update()使用关键字extern重新声明了变量warming,这个关键字的意思是**,通过这个名称使用在外部定义的变量。**
local()函数表明,定义与全局变量同名的局部变量后,局部变量会隐藏全局变量。
如果文件定义了一个静态外部变量,其名称与另一个文件声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量:
//file1
int errors = 20; //external declaration
...
-------------------------------------
//file2
static int errors = 5; //known to file2 only
void froobish(){
cout << errors; //fails
}
//static.cpp -- using a static local variable
#include <iostream>
const int ArSize = 10;
void strcount(const char*);
int main() {
using namespace std;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next);
while (next != '\n')
{
cin.get(next);
}
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next);
while (next != '\n')
cin.get(next);
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize);
}
cout << "Bye\n";
return 0;
}
}
void strcount(const char* str) {
using namespace std;
static int total = 0;
int count = 0;
cout << "\"" << str << "\" contains";
while (*str++) {
count++;
}
total += count;
cout << count << " characters\n";
cout << total << " characters total\n";
}
方法cin.get(input, ArSize)将一直读取输入,直到到达行尾或读取了ArSize - 1个字符为止。他把换行符留在输入队列中。该程序使用cin.get(next)读取输入后的字符。如果next是换行符,则说明cin.get(input, ArSize)读取了整行;否则说明行中还有字符没有被读取。随后,程序用一个循环丢弃余下字符。
cv-限定符
-
const
-
volatile
关键字 volatile 表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。
mutable
他来指出,即使结构(或类)变量为const,其某个成员也可以被修改。
struct data{
char name[30];
mutable int accesses;
...
};
const data veep = {"Claybourne Clodde", 0, ...};
strcpy(veep.name, "Joy"); //not allowed
veep.accesses++; // allowed
veep的const限定符禁止程序修改veep的成员,但access成员的mutable说明符使得access不受这种限制。
再谈const
在c++看来,全局const定义就像使用了static说明符一样。
const int fingers = 10; //same as static const int fingers = 10;
int main(void){
}
C++在哪里查找函数
假设在程序中的某个文件中调用一个函数,C++将到哪里去寻找该函数的定义呢?
- 如果该文件中的函数指出该函数是静态的,则编译器将只在该文件中查找函数定义;
- 否则,编译器将在所有的程序文件中查找。
- 如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义。
- 如果在程序文件中没有找到,编译器将在库中搜索。这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数。
存储方案和动态分配
通常编译器使用三块独立的内存:
- 静态变量
- 自动变量
- 动态存储
使用new运算符初始化
int* pi = new int(6); //set *pi = 6
double * pd = new double(99.99); //*pd set to 99.99
struct where{
double x, y, z;
};
where* one = new where{2.5, 5.3, 7.2}; //c++11
int * ar = new int[4]{2,4,6,7}; // c++11
new失败
按c++标准的话,new失败会抛出bad_alloc异常,但是有些编译器对c++标准支持不是很好,比如vc++6.0中new失败不会抛出异常,而返回0.
//不支持c++标准的做法如下
double *ptr=new double[1000000];
if( 0 == ptr)
……处理失败……
//标准推荐做法一:
try
{
double *ptr=new double[1000000];
}
catch(bad_alloc &memExp)
{
//失败以后,要么abort要么重分配
cerr<<memExp.what()<<endl;
}
标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针:
int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针
if ( p == 0 ) // 如此这般,这个判断就有意义了
return -1;
new: 运算符、函数和替换函数
运算符new 和 new[]分别调用如下函数:
void * operator new (std::size_t); //used by new
void * operator new[](std::size_t); //used by new[]
这些函数被称为分配函数(allocation function),它们位于全局名称空间中。同样,也有由delete 和 delete[] 调用的释放函数(deallocation function);
void * operator delete (void *); //used by new
void * operator delete[](void *); //used by new[]
对于下面这样的基本语句:
int * pi = new int;
被转换为下面这样
int * pi = new (sizeof(int));
而下面的语句
int * pa = new int[40];
被转换为下面这样:
int * pa = new (40 * sizeof(int));
同样
delete pi;
被转换为下面这样:
delete (pi);
有趣的是,c++将这些函数称为可替换的(replaceable)。这意味着如果您有足够的知识和意愿,可为new 和 delete 提供替换函数,并根据需要对其进行定制。例如,可定义作用域为类的替换函数,并对其进行定制,以满足该类的内存分配需求。在代码中,仍将使用new运算符,但他将调用 自己定义的new()函数
定位new运算符
通常,new 负责在堆上找到一个足以能够满足要求的内存块。new 运算符还有一种变体,被称为定位(placement)new 运算符,他让您能够指定要使用的位置。可以使用这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。
要使用 定位new 特性,首先需要包含头文件 new,它提供了这种版本的 new 运算符的原型;然后将new 运算符用于提供了所需地址的参数。 除了需要指定参数外,句法与常规new运算符相同。具体地说,使用定位new 运算符时,变量后面可以有方括号,也可以没有。下面的代码演示了new运算符的4种用法:
#include <new>
struct chaff {
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main() {
chaff* p1, * p2;
int* p3, * p4;
//first, the regular forms of new
p1 = new chaff; //place structure in heap
p3 = new int[20]; // place int array in heap
//now, the two forms of placement new
p2 = new (buffer1)chaff; //place structure in buffer1
p4 = new (buffer2)int[20]; // place int array in buffer2
}
使用常规new运算符和定位new运算符创建动态分配的数组。该程序说明了常规new和定位new的一些重要差别。
//newplace.cpp -- using placement new
#include <iostream>
#include <new> //for placement new
const int BUF = 512;
const int N = 5;
char buffer[BUF]; // chunk of memory
int main() {
using namespace std;
double* pd1, * pd2;
int i;
cout << "Calling new and placement new:\n";
pd1 = new double[N]; //use heap
pd2 = new (buffer)double[N]; //use buffer heap
for ( i = 0; i < N; i++){
pd2[i] = pd1[i] = 1000 + 20.0 * i;
}
cout << "Memory addresses:\n" << " heap: " << pd1
<< " static: " << (void*)buffer << endl;
cout << "Memory contents:\n";
for (i = 0; i < N; i++) {
cout << pd1[i] << " at " << &pd1[i] << "; ";
cout << pd2[i] << " at" << &pd2[i] << endl;
}
cout << "\nCalling new and placement new a second time:\n";
double* pd3, * pd4;
pd3 = new double[N]; // find new address
pd4 = new (buffer) double[N]; //overwrite old data
for (i = 0; i < N; i++) {
pd4[i] = pd3[i] = 1000 + 40.0 * i;
}
cout << "Memory contents:\n";
for (i = 0; i < N; i++) {
cout << pd3[i] << " at " << &pd3[i] << "; ";
cout << pd4[i] << " at" << &pd4[i] << endl;
}
cout << "\nCalling new and placement new a second time:\n";
delete[] pd1;
pd1 = new double[N];
pd2 = new (buffer + N * sizeof(double)) double[N];
for (i = 0; i < N; i++) {
pd2[i] = pd1[i] = 1000 + 60.0 * i;
}
cout << "Memory contents:\n";
for (i = 0; i < N; i++) {
cout << pd1[i] << " at " << &pd1[i] << "; ";
cout << pd2[i] << " at" << &pd2[i] << endl;
}
delete[] pd1;
delete[] pd3;
return 0;
}
第三次调用定位new运算符时,提供了一个从数组buffer开头算起的偏移量,因此将分配新的内存:
pd2 = new (buffer + N * sizeof(double)) double[N]; //offset of 40 bytes
程序清单中没有使用delete来释放使用定位new运算符分配的内存。这个例子中,不能这么做。buffer指定的内存是静态内存,而delete只能用于这样的指针:指向常规new运算符分配的堆内存。也就是说,数组buffer位于delete的管辖区域之外,下面的语句将引发运行阶段的错误:
delete [] pd2; // won't work
此外,如果buffer是使用常规new创建的,那么可以用delete释放。
标准定位new调用一个接收两个参数的new()函数:
int * p1 = new int; //invokes new(sizeof(int));
int * p2 = new(buffer) int; //invokes new(sizeof(int), buffer);
int * p3 = new(buffer) int[40]; //invokes new(40*sizeof(int), buffer);
名称空间
在C++中,提供了名称空间工具,以便更好地控制名称的作用域。
下面代码使用关键字namespace 创建了两个名称空间:Jack和Jill
namespace Jack{
double pail;
void fecth(); //function
int pal;
struct Well{
};
}
namespace Jill{
double bucket(double n){
...
}
double fetch;
int pal;
struct Hill{
...
};
}
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。
访问方式如下:
Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();
namespace Jill{
double bucket(double n){...}
double fetch;
struct Hill{...};
}
char fetch; //global namespace
int main(){
using namespace Jill;
Hill Thrill; // create a type Jill::Hill structure
double water = bucket(2); //use Jill::bucket();
double fetch; // hides Jill::fetch
cin >> fetch; //read a value into local fetch
cin >> ::fetch; //read a value into global fetch
cin >> Jill::fetch;
}
名称空间示例
多文件示例
//namesp.h
#include <string>
//create the pers and debts namespaces
namespace pers {
struct Person {
std::string fname;
std::string lname;
};
void getPerson(Person&);
void showPerson(const Person&);
}
namespace debts {
using namespace pers;
struct Debt {
Person name;
double amount;
};
void getDebt(Debt&);
void showDebt(const Debt &);
double sumDebts(const Debt ar[], int n);
}
//namesp.cpp
/*
提供了头文件中的函数原型对应的定义。
在名称空间中声明的函数名的作用域为整个名称空间,
因此定义和声明必须位于同一个名称空间中。
*/
#include <iostream>
#include "namesp.h"
namespace pers {
using std::cout;
using std::cin;
void getPerson(Person& rp) {
cout << "Enter first name: ";
cin >> rp.fname;
cout << "Enter last name: ";
cin >> rp.lname;
}
void showPerson(const Person& rp) {
std::cout << rp.lname << ", " << rp.fname;
}
}
namespace debts {
void getDebt(Debt& rd) {
getPerson(rd.name);
std::cout << "Enter debt: ";
std::cin >> rd.amount;
}
void showDebt(const Debt& rd) {
showPerson(rd.name);
std::cout << ": $" << rd.amount << std::endl;
}
double sumDebts(const Debt ar[], int n) {
double total = 0;
for (int i = 0; i < n; i++) {
total += ar[i].amount;
}
return total;
}
}
//usenmsp.cpp -- using namespace;
#include <iostream>
#include "namesp.h"
void other();
void another();
int main() {
using debts::Debt;
using debts::showDebt;
Debt golf = { {"Benny", "Goatsniff"}, 120.0 };
showDebt(golf);
other();
another();
return 0;
}
void other() {
using std::cout;
using std::endl;
using namespace debts;
Person dg = { "Doodles", "Glister" };
showPerson(dg);
cout << endl;
Debt zippy[3];
int i;
for (i = 0; i < 3; i++) {
getDebt(zippy[i]);
}
for (i = 0; i < 3; i++) {
showDebt(zippy[i]);
}
cout << "Total debt:$" << sumDebts(zippy, 3) << endl;
return;
}
void another() {
using pers::Person;
Person collector = { "Milo", "Rightshift" };
pers::showPerson(collector);
std::cout << std::endl;
}