文章目录
前言
本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。
一、单独编译
不要将函数定义或变量声明放在头文件中,这在简单情形下可能是可行的,但这样通常会造成麻烦。例如,如果在头文件中包含一个函数定义,然后在其它两个文件中(属于同一程序)包含该头文件,则同一个程序将包含两次这个函数的定义,除非是内联函数,否则将出错。头文件中常包含的内容:
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
头文件的名称如果是包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找该头文件;如果包含在双引号中,则会首先在当前的工作目录或源代码目录中查找。
在同一个文件中只能将同一个头文件包含一次,下面的代码可以防止包含同一个头文件两次而出错:
#ifndef HEAD_H_
#define HEAD_H_
…
#endif
省略号代表的是头文件的正式内容,假设该头文件是head.h。如果在同一个文件中包含了该头文件两次:
#include”head.h”
…
#include”head.h”
编译到第一个#include时,HEAD_H_未定义,因此对其定义;编译到第二个#include时,发现HEAD_H_已经定义过,在该头文件中会直接执行#endif后面的语句,避免了编译同一个头文件两次。
C++允许每个编译器设计人员以他自己的喜好实现名称修饰,所以对于同一个函数,不同编译器中的名称修饰很有可能是不同的,在一个编译器中调用由另一个编译器定义的函数有极大概率会出错,如果有函数定义的源代码,建议在同一个编译器中重新编译以消除这种错误。
二、存储持续性、作用域、链接性
C++有4中不同的方案来存储数据:
- 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性是自动的,它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时被释放。
- 静态存储持续性:在函数外部定义的变量和使用关键字static定义的变量的存储持续性是静态的,它们在程序整个运行过程中都存在。
- 线程存储持续性(C++11):当前有很多多核处理器,这些CPU可以同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是用关键字thread_local声明的,则其生命周期与所属的线程一样长。
- 动态存储持续性:用new运算符分配的内存将一直存在,直到用delete将其释放。这种内存的存储持续性是动态的,也被称为自由存储或堆。
1.作用域和链接
作用域描述了名称在文件的多大范围内可见;链接性描述了名称如何在不同文件间共享,链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。
2.静态持续变量
静态存储持续性变量有3中链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)、无链接性(只能在当前函数或代码块中访问),这3种链接性都在整个程序执行期间存在。静态变量如果没有被显示初始化,编译器将把它设置为0,在默认情况下,静态数组和结构将每个元素或成员的所有位置都设置为0。
要创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,且使用static限定符;要创建没有链接性的静态持续变量,必须在代码块的内部声明它且使用static限定符。例如:
…
int global = 1000; //静态持续变量,外部链接性
static int_one_file = 50; //静态持续变量,内部链接性
int main()
{
…
}
void fun1(int n)
{
static int count = 0; //静态持续变量,无链接性
…
}
5种变量存储方式:
3.静态持续性、外部链接性
C++提供了两种变量声明,一种是定义声明或简称为定义,它给变量分配存储空间;另一种是引用声明或简称为引用,它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,不进行初始化,否则将视为定义而导致分配空间。如果在各个文件中使用一个外部变量,只需在一个文件中进行定义,在需要使用这个变量的其他文件中使用关键字extern对其进行声明。
int cats = 20; //定义
int dogs = 22; //定义
int fleas; //定义
…
extern int cats; //引用
extern int dogs; //引用
…
extern int cats; //引用
extern int dogs; //引用
extern int fleas; //引用
file02.cpp声明了在file01.cpp中定义的cats和dogs,但是没有声明fleas,因此对其没有访问权限;file03.cpp声明了cats、dogs和fleas,因此都可以使用它们。
可以看下面的两个文件:
…
double warming = 0.3; //定义全局变量
void update(double dt);
void local();
int main()
{
…
}
…
extern double warming; //引用在另一个文件中定义的全局变量
…
void update(double dt)
{
extern double warming; //使用全局变量
warming += dt;
…
}
void local()
{
double warming = 0.8; //定义局部变量,隐藏了同名的全局变量
cout << “Local warming : ” << warming << endl;
cout << “Global warming : ” << ::warming << endl; //使用作用域解析运算符::,放在变量名前表示使用变量的全局版本
}
4.静态持续性、内部链接性
如果文件定义了一个静态外部变量,其名称与另一个文件中定义的全局变量相同,则在该文件中静态变量将隐藏全局变量:
//file1
int errors = 20; //全局变量
……
//file2
static int errors = 5;//静态外部变量,隐藏全局变量,只能由该文件访问
……
5.静态持续性、无链接性
将static限定符用于在代码块中定义的变量,则该变量只能在该代码块中使用,而且该变量将一直存在,即使该代码块已经执行完毕。如果初始化了静态局部变量,则程序只在启动时进行一次初始化,之后再调用函数时将不会再进行初始化:
void function()
{
…
static int total = 0;
…
total++;
}
6.const全局变量
const全局变量的链接性是内部的,如果想在另一个文件中使用它,要用extern关键字来引用:
//1.cpp
const int states = 10; //与static const int states = 10相同
//2.cpp
extern const int states = 10;
7.函数的链接性
和变量一样,函数也有链接性,C++不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序执行期间都一直存在。在默认情况下,函数的链接性为外部的,即可以在文件间共享。实际上,可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的,不过这是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是由链接程序搜索的库文件)。还可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用。必须同时在原型和函数定义中使用该关键字:
static int private(double x);
……
static int private(double x)
{
……
}
这意味着这个函数只能在该文件中使用,可以在另一个文件中定义一个同名的函数。
8.定位new运算符
通常,new负责在堆(heap)中寻找一个能够满足要求的内存块,new还有一种变体,用来分配指定位置的内存。
要使用new的定位特性,需要包含头文件<new>。除需要指定参数外,句法与常规new运算符相同。
#include <new>
struct chaff
{
char dross[20];
int slat;
};
char buffer1[50];
char buffer2[500];
int main()
{
chaff *p1, *p2;
int *p3, *p4;
p1 = new chaff; //从堆(heap)中分配内存
p3 = new int; //从堆(heap)中分配内存
p2 = new (buffer1) chaff; //从buffer1中分配内存
p4 = new (buffer2) chaff; //从buffer2中分配内存
}
分析下面的代码:
#include <iostream>
#include <new>
using namespace std;
const int N = 5;
const int BUF = 512;
char buffer[BUF];
int main()
{
double *pd1, *pd2;
int i;
cout << "调用定位new运算符:\n";
pd1 = new double[N]; //在堆(heap)中分配内存
pd2 = new (buffer) double[N]; //在buffer中分配内存
for (i = 0; i < N; i++)
pd2[i] = pd1[i] = 1000 + 20.0 * i;
cout << "存储地址:\n";
cout << " heap: " << pd1 << "; buffer: " << (void *)buffer << endl;//用(void *)对buffer进行强制类型转换,防止输出字符串,因为buffer是char类型的字符串数组
for (i = 0; i < N; i++)
{
cout << pd1[i] << " @ " << &pd1[i] << "; "
<< pd2[i] << " @ " << &pd2[i] << endl;
}
cout << "\n第二次调用定位new运算符:\n";
double *pd3, *pd4;
pd3 = new double[N];
pd4 = new (buffer) double[N];
for (i = 0; i < N; i++)
pd4[i] = pd3[i] = 1000 + 40.0 * i;
cout << "存储地址:\n";
for (i = 0; i < N; i++)
cout << pd3[i] << " @ " << &pd3[i] << "; "
<< pd4[i] << " @ " << &pd4[i] << endl;
cout << "\n第三次调用定位new运算符:\n";
delete[] pd1;
pd1 = new double[N];
pd2 = new (buffer + N * sizeof(double)) double[N];//将从buffer开始算起偏移N * sizeof(double)的位置分配给pd2
for (i = 0; i < N; i++)
pd2[i] = pd1[i] = 1000 + 60.0 * i;
cout << "存储地址:\n";
for (i = 0; i < N; i++)
cout << pd1[i] << " @ " << &pd1[i] << "; "
<< pd2[i] << " @ " << &pd2[i] << endl;
delete[] pd1;
delete[] pd3; //pd2和pd4使用的是buffer的静态内存,delete只能释放由new分配的堆内存,所以pd3和pd4不能用delete释放
return 0;
}
运行结果如下图:
示例中没有使用delete释放使用定位new运算符分配的内存,实际是因为在这个示例中不能这样做。buffer的内存是静态内存,而delete只能用于常规new运算符分配的堆内存。另一方面,如果buffer是使用常规new运算符创建的,便可以使用常规delete运算符来释放整个内存块。
定位new运算符的基本原理是返回传递给它的地址,并将其转换为void *,以便能够赋给各种指针类型。但这说的是默认定位new函数,C++允许程序员重载定位new函数。
三、名称空间
1.名称空间的特性
下面使用了关键词namespace创建了两个名称空间:Jack和Jill。
namespace Jack
{
double pail;
void fetch();
int pal;
struct well {……};
}
namespace Jill
{
double bucket(double n) {……}
double fetch;
int pal;
struct Hill {……};
}
名称空间是全局的,也可以位于另一个名称空间中,但不能位于代码块中。名称空间是开放的,即可以把名称加入到已有的名称空间中,例如,下面代码将goose加入到名称空间Jill中:
namespace Jill
{
char * goose(const char *);
}
原来的名称空间Jack为函数fetch()提供了原型,可以在该文件后面(或者另一个文件中)再次使用名称空间Jack位其提供定义:
namespace Jack
{
void fetch()
{
……
}
}
通过作用域解析运算符::,使用名称空间来限定改名称:
Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();
1.1using声明和using编译指令
using声明使特定的标识符可用,using编译指令使整个名称空间可用。
char fetch;
int main()
{
using Jill::fetch; //使名称空间Jill中的fetch在main()中可用
cin >> fetch; //读入Jill::fetch的值
cin >> ::fetch; //读入全局变量fetch的值,即char fetch
}
using 编译指令使整个名称空间中的名称都可用:
using namespace Jack;
1.2using声明和using编译指令的比较
如果某个名称已经在函数中声明了,则不能使用using声明导入相同的名称;使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名。
char fetch;
int main()
{
using namespace Jill;
double fetch; //隐藏Jill::fetch
cin >> fetch; //读入局部变量fetch的值
cin >> ::fetch; //读入全局变量(char fetch)的值
cin >> Jill::fetch; //读入Jill::fetch的值
}
1.3名称空间的其他特性
名称空间的嵌套:
namespace elements
{
namespace fire
{
int flame;
……
}
float water;
}
flame指的是elements:🔥:flame。
可以在名称空间中使用using编译指令和using声明:
namespace myth
{
using Jill::fetch;
using namespace elements;
}
Jill::fetch现在位于名称空间myth中,要访问它可以这样:myth::fetch,它也位于Jill中,也可以这样使用:std::cin >> Jill::fetch.
using namespace myth相当于:
using namespace myth;
using namespace elements;
using namespace fire;
因为fire位于elements中,elements位于myth中。
可以给名称空间创建别名:
namespace my_very_favorite_things {……};
namespace mvft = my_very_favorite_things;
namespace MEF = myth::elements::fire;
using MEF::flame;
1.4未命名的名称空间
可以创建没有名称的名称空间:
namespace //没有名称
{
int ice;
int bandycoot;
}
该名称空间不能在其他位置使用,只能在该文件中使用名称空间中的名称,这成为了链接性为内部的静态变量的替代品:
static int counts; //静态变量,内部链接性
int main()
{
……
}
采用名称空间的方法如下:
namespace
{
int counts;
}
int main()
{
……
}
2.名称空间示例
#pragma once
#ifndef NAMESP_H_
#define NAMESP_H_
#include <string>
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);
}
#endif // !NAMESP_H_
#include <iostream>
#include "namesp.h"
namespace pers
{
void getPerson(Person &rp)
{
std::cout << "输入fname:";
std::cin >> rp.fname;
std::cout << "输入lname:";
std::cin >> rp.lname;
}
void showPerson(const Person &rp)
{
std::cout << rp.fname << "." << rp.lname;
}
}
namespace debts
{
void getDebt(Debt &rd)
{
getPerson(rd.name);
std::cout << "输入amount:";
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 sum = 0;
for (int i = 0; i < n; i++)
{
sum += ar[i].amount;
}
return sum;
}
}
#include <iostream>
#include "namesp.h"
int main()
{
using std::cin;
using std::cout;
using std::endl;
debts::Debt family[2];
cout << "输入第一个成员的信息:\n";
debts::getDebt(family[0]);
cout << "\n输入第二个成员的信息:\n";
debts::getDebt(family[1]);
cout << "\n第一个成员:\n";
debts::showDebt(family[0]);
cout << "\n第二个成员:\n";
debts::showDebt(family[1]);
double sum;
sum = debts::sumDebts(family, 2);
cout << "总资产: $" << sum << endl;
return 0;
}
总结
以上就是本文的内容——C++的内存模型和名称空间,包括变量存储的持续性、作用域、链接性和名称空间的特性和使用。