一个文件(头文件)包含了用户定义类型的定义;另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成了一个软件包,可用于各种程序中。
头文件中常包含的内容:
包含自己的头文件时,应使用引号而不是尖括号:
// CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(PrimerPlus)
set(CMAKE_CXX_STANDARD 11)
include_directories(chapter_9) // 头文件路径
// 将file1和file2一起编译链接,只要两个文件包含相同的头文件,且file2中定义的函数在头文件中声明,则file1可以调用file2的函数且不需要声明
add_executable(file1 chapter_9/file1.cpp chapter_9/file2.cpp)
// coordin.h -- structure templates and function prototypes
// structure templates
// 仅当以前没有使用预处理器编译指令#define定义名称COORDINH时,才处理#ifndef和#endif之间的语句:
#ifndef PRIMERPLUS_COORDIN_H // PRIMERPLUS是工程的名字
#define PRIMERPLUS_COORDIN_H
#endif
如果有两个同名的变量(一个位于外部代码块中,另一个位于内部代码块中),情况将如何呢?
新的定义隐藏了(hide)以前的定义,新定义可见,旧定义暂时不可见。在程序离开该代码块时,原来的定义又重新可见。如下图所示
在C++11中,关键字auto用于自动类型推断。
但在C语言和以前的C++版本中,auto的含义截然不同,它用于显式地指出变量为自动存储。
可以使用任何在声明时其值为已知的表达式来初始化自动变量。
register int count_fast;C++也为静态存储持续性变量提供了3种链接性:
int global = 1000; // static duration, external linkage
static int one_file = 50; // static duration, internal linkage
int main()
{
...
}
void funct1(int n)
{
static int count = 0; // static duration, no linkage
// 即使在 funct1( )函数没有被执行时,count也留在内存中。
int llama = 0;
...
}
静态变量初始化:
int a; 和常量表达式初始化int b=5;#include
int x; // zero-initialization
int y = 5; // constant-expression initialization
long z = 13 * 13; // constant-expression initialization
const double pi = 4.0 * atan(1.0); // dynamic initialization
int enough = 2 * sizeof (long) + 1; // constant expression initialization
C++有“单定义规则”(One Definition Rule,ODR),该规则指出,变量只能有一次定义。
C++提供了两种变量声明:
double up; // definition, up is 0
extern int blem; // blem defined elsewhere
extern char gr = 'z'; // definition because initialized
如果在函数中声明了一个与外部变量同名的变量,结果将如何呢?
这种声明将被视为一个自动变量的定义,当程序执行自动变量所属的函数时,该变量将位于作用域内。
作用域解析运算符(::) 放在变量名前面时,该运算符表示使用变量的全局版本。
int a = 10;
void main(void)
{
int a = 5;
cout << a << endl; // a = 5;
cout << ::a << endl;// a = 10;
}
外部存储尤其适于表示常量数据,因为这样可以使用关键字const 来防止数据被修改。
const char * const months[12] =
{
"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October",
"November", "December"
};
// 第一个const防止字符串被修改,
// 第二个const确保数组中每个指针始终指 向它最初指向的字符串。
// file1
int errors = 20; // external declaration
...
---------------------------------------------
// file2
static int errors = 5; // known to file2 only
// int errors = 10; //这种做法是错误的,违反了单定义规则
void froobish()
{
cout << errors; // uses errors defined in file2
...
// 使用cin.get(next)读取行输入后的字符。
// 如果next是换行符,则说明 cin.get(input, ArSize)读取了整行;
// 否则说明行中还有字符没有被读取。 随后,程序使用一个循环来丢弃余下的字符。
// static.cpp -- using a static local variable
#include
const int ArSize = 20; // constants
void strcount(const char * str); // function prototype
int main()
{
using namespace std;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next); // 使用cin.get(next)读取行输入后的字符。
while (next != '\n') // string didn't fit!
cin.get(next); // dispose of remainder
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize); // 使用get(char *, int)读取空行将导致cin为false。
}
cout << "Bye\n";
return 0;
}
void strcount(const char * str)
{
using namespace std;
static int total = 0; // static local variable
int count = 0; // automatic local variable
cout << "\"" << str <<"\" contains ";
while (*str++) // go to end of string
count++;
total += count;
cout << count << " characters\n";
cout << total << " characters total\n";
}
struct data
{
char name[30];
mutable int accesses; // 即使声明一个const的结构,也可以对其中的accesses进行修改
...
};
const data veep = {"Claybourne Clodde", 0, ... };
// strcpy(veep.name, "Joye Joux"); // not allowed
veep.accesses++; // allowed
// file1
int a = 5; // 链接为外部,extern能省略。
const int b = 6; // 链接为内部,只能file1文件使用
extern const int f = 10;// 链接为外部,extern不能省略。
static const int c = 7; // 链接为内部,只能file1文件使用,c不能改变
static int e = 9; // 链接为内部,只能file1文件使用,c可以改变
{
static int d = 8; // 无链接
}
--------------------------------
// file2
extern int a; // 引用声明
extern const int f; // 引用const声明
--------------------------------
// xxx.h
// 只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量。
// const全局变量的链接性为内部,不违背“单定义规则”
const int g = 11;
const int h = 12;
链接程序要求每个不同的函数都有不同的符号名(内部表示)。
// 在C++程序中使用C库中预编译的函数,可以用函数原型来指出要使用的约定:
extern "C" void spiff(int); // use C protocol for name look-up
extern void spoff(int); // use C++ protocol for name look-up
extern "C++" void spaff(int); // use C++ protocol for name look-up
通常,编译器使用三块独立的内存:一块用于静态变量(可能再细分),一块用于自动变量,另外一块用于动态存储。
存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。
C++98,如果要为内置的标量类型(如int或double)分配存储空间并初始化,可在类型名后面加上初始值,并将其用括号括起:
int *pi = new int (6); // *pi set to 6
double * pd = new double (99.99); // *pd set to 99.99
在C++11中,可初始化常规结构或数组,还可将列表初始化用于单值变量:
struct where {double x; double y; double z;};
where * one = new where {2.5, 5.3, 7.2}; // C++11
int * ar = new int [4] {2,4,6,7}; // C++11
int *pin = new int {}; // *pi set to 0
double * pdo = new double {99.99}; // *pd set to 99.99
new可能找不到请求的内存量。在最初的10年中,C++在这种情况下让new返回空指针,但现在将引发异常std::bad_alloc。
void * operator new(std::size_t); // used by new
void * operator new[](std::size_t); // used by new[]
void operator delete(void *);
void operator delete[](void *);
// 运算符重载,std::size_t是一个 typedef
int * pi = new int;
int * pi = new(sizeof(int));
int * pa = new int[40];
int * pa = new(40 * sizeof(int));
delete pi;
delete (pi);
通常,new负责在堆(heap)中找到一个足以能够满足要求的内存块。
new运算符还有另一种变体,被称为定位(placement)new运算符,它让您能够指定要使用的位置。
#include #include // 提供了这种版本的new运算符的原型
struct chaff
{
char dross[20];
int slag;
};
char buffer1[50]; // 使用两个静态数组来为定位new运算符提供内存空间。
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函数不可替换,但可重载。它至少需要接收两个参数,其中第一个总是std::size_t,指定了请求的字节数。
int * pi = 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
{
double pail; // variable declaration
void fetch(); // function prototype
int pal; // variable declaration
struct Well { ... }; // structure declaration
}
namespace Jill
{
double bucket(double n) { ... } // function definition
double fetch; // variable declaration
int pal; // variable declaration
struct Hill { ... }; // structure declaration
}
namespace Jill
{
char * goose(const char *); // 名称空间中添加变量
}
namespace Jack
{
void fetch() // 函数定义
{...}
}
Jack::pail = 12.34; // use a variable
Jill::Hill mole; // create a type Hill structure
Jack::fetch(); // use a function
using namespace std;using Jill::fetch;// using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch; // a using declaration
// using声明将特定的名称添加到它所属的声明区域中
namespace Jill {
double bucket(double n) { ... }
double fetch;
struct Hill { ... };
}
char fetch;
int main()
{
using Jill::fetch; // put fetch into local namespace
double fetch; // Error! Already have a local fetch
cin >> fetch; // read a value into Jill::fetch
cin >> ::fetch; // read a value into global fetch
...
}
// 编译器不允许同时使用两个using声明相同名称变量,可以使用作用域解析运算符。
jack::pal = 3;
jill::pal =10;
// 编译器不允许同时使用两个using声明相同名称变量
// using jack::pal;
// using jill::pal;
假设名称空间和声明区域定义了相同的名称:
namespace Jill {
double bucket(double n) { ... }
double fetch;
struct Hill { ... };
}
char fetch; // global namespace
int main()
{
using namespace Jill; // import all namespace names
Hill Thrill; // create a type Jill::Hill structure
double water = bucket(2); // use Jill::bucket();
double fetch; // not an error; hides Jill::fetch
cin >> fetch; // read a value into the local fetch
cin >> ::fetch; // read a value into global fetch
cin >> Jill::fetch; // read a value into Jill::fetch
...
}
int foom()
{
// Hill top; // ERROR
// Jill::Hill crest; // valid
using Jill::Hill crest;
}
// 可以用嵌套式名称空间来创建一个包含常用using声明的名称空间:
namespace elements
{
namespace fire
{
int flame;
...
}
float water;
}
// 使用下面的using编译指令使内部名称可用:
using namespace elements::fire;
// 在名称空间中使用using编译指令和using声明:
namespace myth
{
using Jill::fetch;
using namespace elements;
using std::cout;
using std::cin;
}
std::cin >> myth::fetch; // 可以这样访问fetch
std::cout << Jill::fetch; // 也可以这样访问fetch
// 可以给名称空间创建别名:
namespace my_very_favorite_things { ... };
namespace mvft = my_very_favorite_things;
namespace MEF = myth::elements::fire;
using MEF::flame;
static int counts; // 全局静态变量,链接为内部。
// 可以用下面的替代
namespace
{
int counts; // static storage, internal linkage
}
2.using声明和using编译指令之间有何区别?
5、在一个文件中调用average(3, 6)函数时,它返回两个int参数的int平均值,在同一个程序的另一个文件中调用时,它返回两个int参数的 double平均值。应如何实现?
可以在每个文件中包含单独的静态函数定义。或者每个文件都在未命名的名称空间中定义一个合适的average( )函数,在该函数前加static。
2、判断输入是否为空
const ArSize = 20;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next); // 使用cin.get(next)读取行输入后的字符。
while (next != '\n') // string didn't fit!
cin.get(next); // dispose of remainder
cout << input << endl;
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize); // 使用get(char *, int)读取空行将导致cin为false。
}
-------------------------------
char name[20];
cin.getline(name, 20);
if (strcmp(name, "") == 0)
break;
string input;
cout << "Enter a line:\n";
getline(cin, input);
while (input != "") // 这里注意
{
strcount(input);
cout << "Enter next line (empty line to quit):\n";
getline(cin, input);
}
------------------
string fullname;
getline(cin, fullname);
if (strcmp(name, "") == 0)
break;
// 3、编写一个程序,使用定位new运算符将一个包含两个这种结构的数组放在一个缓冲区中。
// 然后,给结构的成员赋值(对于char数组,使用函数strcpy( )),并使用一个循环来显示内容。
// 一种方法是像程序清单 9.10那样将一个静态数组用作缓冲区;
// 另一种方法是使用常规new运算符来分配缓冲区。
#include
#include
#include
using namespace std;
struct chaff
{
char dross[20];
int slag;
};
const int BUF = 200;
char buffer[BUF];
void show(const chaff &p);
int main(void)
{
chaff * pd1 = new chaff[2];
chaff * pd2 = new(buffer) chaff[2]; // 使用定位new运算符
char dross[20];
int slag;
for (int i=0; i<2; i++)
{
cout << "#" << i+1 << " :" << endl;
cout << "Enter the dross:";
cin.getline(dross, 20);
cout << "Enter the slag:";
cin >> slag;
cin.get(); // 这里要注意,消耗回车
strcpy(pd1[i].dross, dross);
strcpy(pd2[i].dross, dross);
pd1[i].slag = slag;
pd2[i].slag = slag;
}
for (int i=0; i<2; i++)
{
show(pd1[i]);
show(pd2[i]);
}
delete [] pd1; // 这里只能释放动态
return 0;
}
void show(const chaff &p) // 这里用结构体引用
{
cout << "The dross is: " << p.dross << endl;
cout << "The slag is:" << p.slag << endl;
}