C++杂七杂八小知识和一些基础语法
C++杂七杂八小知识和一些基础语法
头文件
C语言的传统是,头文件使用扩展名,将其作为一种通过名称标识文件类型的简单方式。
例如,头文件math.h支持各种C语言数学函数,但C++的用法变了。现在,对老式C的头文件保留了扩展名h(C++程序仍可以使用这种文件),而C++头文件则没有扩展名。
有些C头文件被转换为C++头文件,这些文件被重新命名,去掉了扩展名h(使之成为C++风格的名称),并在文件名称前面加上前缀c(表明来自C语言)。例如,C+版本的math.h为cmath。
有时C头文件的C版本和C+版本相同,而有时候新版本做了一些修改。对于纯粹的C++头文件(如iostream)来说,去掉h不只是形式上的变化,没有h的头文件也可以包含名称空间.
然后说明一下#include<>和#include""的区别,<>是直接去系统的库源文件找,""是去当前目录找,如果没有再去库源文件找.
与C不同的关键字
const
const int *const p
这个变量的声明,这样去解析:
- p: 它名叫P
- const p :p是常量,它不可变。
- *const p : p是常量指针,它不可变。
- int *const p :p是常量指针,它不可变,它指向一个int。
- const int *const p :p是常量指针,它不可变,它指向一个常量int。
注意void f(int a){}
和void f(const int a){}
不构成重载,原因是这里是拷贝,所以实际上是一样的,如果你用引用就可以了.
constexpr
老标准中编译器无法判断常量表达式,故而不能直接是用此类东西初始化数组之类.
C++11 提供了 constexpr
让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证 len_foo
在编译期就应该是一个常量表达式。
此外,constexpr
修饰的函数可以使用递归:
constexpr int fibonacci(const int n) {
return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
}
需要说明一下,如果不是需要常量表达式的地方,函数依旧是运行时计算,除非你在那个地方强制再次使用constexpr.
在此之上,还有if constexpr的特殊用法.一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高?C++17 将 constexpr
这个关键字引入到 if
语句中,允许在代码中声明常量表达式的判断条件,考虑下面的代码:
#include <iostream>
template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value) {
return t + 1;
} else {
return t + 0.001;
}
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
在编译时,实际代码就会表现为如下:
int print_type_info(const int& t) {
return t + 1;
}
double print_type_info(const double& t) {
return t + 0.001;
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
与C不同的控制流结构
if/switch可直接定义变量
C++17 后,我们可以在 if
(或 switch
)中定义变量:
// 将临时变量放到 if 语句内
if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3);
itr != vec.end()) {
*itr = 4;
}
for区间
C++11中,for允许对数组类或容器类(vector,array等等)的每个元素执行相同的操作:
double prices[5] {1.22, 2.22, 3.33, 4.44, 5.55};
for (double x : prices)
cout << x << std::endl;
如果要修改元素则需要 for(double &x : prices)
还有基于列表和范围的初始化方式:
for(int x : {1,2,3})
cout << x << "";
cout << endl;
作用域,持续性和链接
单独编译
为了在链接的时候不报错,比如说两个文件都用了同一个结构声明,故而他们开发了#include
,让头文件存放结构定义.
但是函数定义不要放在头文件,如果头文件包含一个函数定义,两个文件包含同一个头文件,则同一个程序将包含同一个函数的两个定义.
结构应该如下:
- 头文件:包含结构声明和使用这些结构的函数原型:
- 函数原型
- 使用#define或者const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
- 源代码文件:包含结构有关的函数代码.
- 源代码文件:包含调用与结构相关的函数的代码.
以上讲的太过专业,我让ds翻译了一下:
这段话主要解释了在 C/C++ 程序中如何通过头文件(Header File)来组织代码,避免重复和错误。以下是关键点解释:
核心概念
- 问题背景
当把一个程序拆分成多个源代码文件时,如果多个文件都需要用同一个结构(如struct
)或函数,直接在每个文件中重复声明结构会导致:
- 重复劳动:每次修改结构都要修改所有文件
- 易出错:可能漏改某些文件,导致不一致
- 解决方案:头文件
- 头文件的作用:将需要共享的声明(如结构、函数原型)集中放在一个头文件中。
- 其他文件通过
#include
包含头文件,无需重复写声明。
头文件应该放什么?
✅ 可以放的内容
- 结构声明(如
struct Point { ... };
)- 函数原型(如
double calculate(Point p);
)- 符号常量(如
#define PI 3.14
或const int MAX=100;
)- 类声明(C++)
- 模板声明(C++)
- 内联函数(如
inline int add(int a, int b) { ... }
)❌ 不要放的内容
- 普通函数定义(如
void print() { ... }
)
原因:多个文件包含该头文件时,会导致函数重复定义。- 变量声明(如
int globalVar;
)
原因:多个文件包含该头文件时,会导致变量重复定义。
为什么结构声明可以放头文件?
结构声明只是模板
,不会实际创建变量或占用内存。例如:
struct Point { // 声明一个结构类型 double x;
double y; }; ``` 其他文件包含这个头文件后,只是知道了如何创建Point类型的变量,不会引发冲突。
实际代码组织示例
假设程序需要处理坐标转换,可以分成以下文件:
头文件
coordinate.h
// 结构声明 struct Point { double x; double y; }; // 函数原型 double toPolar(Point p); // 直角坐标转极坐标 void print(Point p); // 打印坐标
源代码文件
coordinate.cpp
#include "coordinate.h" // 函数的具体实现 double toPolar(Point p) { return sqrt(p.x * p.x + p.y * p.y); } void print(Point p) { printf("(%f, %f)\n", p.x, p.y); }
#include "coordinate.h" int main() { Point p = {3.0, 4.0}; print(p); return 0; }
这样做的好处
- 避免重复:所有文件共享同一份声明。
- 易于维护:修改结构时只需改头文件。
- 代码复用:其他程序可以直接包含头文件和使用函数库。
补充说明
内联函数和
const
变量
由于它们有特殊的链接属性(不会导致重复定义),可以安全放在头文件中。防止头文件重复包含
使用预处理指令避免重复包含:
#ifndef COORDINATE_H #define COORDINATE_H // 头文件内容 #endif
后续通过链接链接在一起.
C++在哪里查找函数
假设在程序的某个文件中调用一个函数,C++将到哪里去寻找该函数的定义呢?如果该文件中的函数原型指出该函数是静态的,则编译器将只在该文件中查找函数定义;否则,编译器(包括链接程序)将在所有的程序文件中查找。如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义。
如果在程序文件中没有找到,编译器将在库中搜索。这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数(然而,C++保留了标准库函数的名称,即程序员不应使用它们)。有些编译器-链接程序要求显式地指出要搜索哪些库。
头文件管理
在同一个文件中只能将同一个头文件包含一次。记住这个规则很容易,但很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的CC++技术可以避免多次包含同一个头文件。它是基于预处理器编译指令ifndef(即if not defined)的。下面的代码片段意味着仅当以前没有使用预处理器编译指令#define定义名称COORDIN H时,才处理#ifndef和#endif之间的语句:
#ifndef COORDIN_H_
通常,使用#define语句来创建符号常量,如下所示:
#define MAXIMUM 4096
但只要将#define用于名称,就足以完成该名称的定义,如下所示:
#define COORDIN_H_
程序清单9.1使用这种技术是为了将文件内容包含在ifndef中:
#ifndef COORDIN_H_
#define COORDIN_H_
//place include file contents here
#endif
编译器首次遇到该文件时,名称COORDIN_H_ 没有定义(我们根据include文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称)。在这种情况下,编译器将查看ifndef和#endif之间的内容(这正是我们希望的),并读取定义COORDIN H的一行。如果在同一个文件中遇到其他包含coordin.h的代码,编译器将知道COORDIN_H_ 已经被定义了,从而跳到endf后面的一行上。注意,这种方法并不能防止编译器将文件包含两次,而只是让它忽略除第一次包含之外的所有内容。大多数标准C和C++头文件都使用这种防护(guarding)方案。否则,可能在一个文件中定义同一个结构两次,这将导致编译错误。
存储持续性
- 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
- 静态存储持续性:在函数定义外定义的变量和使用关键字
static
定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。 - 线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字
thread local(可以和extern和static一起用)
声明的,则其生命周期与所属的线程一样长。本书不探讨并行编程。 - 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store.)或堆(heap)。
自动存储持续性
在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。
也就是说,如果在main()中声明了一个名为texas的变量,并在函数oil()中也声明了一个名为texas变量,则创建了两个独立的变量一只有在定义它们的函数中才能使用它们。对oil()中的txas执行的任何操作都不会影响main()中的texas,反之亦然。另外,当程序开始执行这些变量所属的代码块时,将为其分配内存;当函数结束时,这些变量都将消失(注意,执行到代码块时,将为变量分配内存,但其作用域的起点为其声明位置)。
如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。例如,假设在main()的开头定义了一个名为teledeli的变量,然后在main()中开始一个新的代码块,并其中定义了一个新的变量websight,,则teledeli在内部代码块和外部代码块中都是可见的,而websight就只在内部代码块中可见,它的作用域是从定义它的位置到该代码块的结尾.
普通变量作用域与c是一致的.
register
可以使变量为寄存器变量,但是这个用法已经失效了,变成与原本auto一样的用法,就是声明一个变量是自动变量.
当然要是想使用全局变量而非局部变量可以用::
来修饰获得全局变量.
int a = 100;
int main(){
int a = 101;
cout << a << endl;
cout << ::a << endl;
}
静态存储持续性
外部链接性(在其他文件可以访问),内部链接性(只在当前文件才能访问),无链接性(只能在当前函数和代码块中访问)
等于说,这个变量的访问位置还是那些个地方,但是他不会被摧毁.
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字 static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
静态变量的初始化
#include<cmath>
int x;
int y = 5;
const double pi = 4.0 * atan(1.0);
这里他们都会被初始化为0,再由编译器计算,而pi则需要动态计算.
静态持续性,外部链接性
全局变量就是如此.
而cpp是只允许一次定义,要引用被定义的变量怎么办呢?
可以用extern
关键字,这样不会分配空间,只会引用.
说明符
const
在C++(但不是在C语言)中,const限定符对默认存储类型稍有影响。在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。也就是说,在C+看来,全局const定义(如下述代码段所示)就像使用了static说明符一样。
const int fingers 10;
//same as static const int fingers =10;
int main(void)
{ ...
C++修改了常量类型的规则,让程序员更轻松。例如,假设将一组常量放在头文件中,并在同一个程序的多个文件中使用该头文件。那么,预处理器将头文件的内容包含到每个源文件中后,所有的源文件都将包含类似下面这样的定义:
const int fingers 10;
const char warning "Wak!";
如果全局const
声明的链接性像常规变量那样是外部的,则根据单定义规则,这将出错。也就是说,只能有一个文件可以包含前面的声明,而其他文件必须使用extern
关键字来提供引用声明。另外,只有未使用extern关键字的声明才能进行初始化:
//extern would be required if const had external linkage
extern const int fingers;
//can't be initialized
extern const char warning;
因此,需要为某个文件使用一组定义,而其他文件使用另一组声明。然而,由于外部定义的const
数据的链接性为内部的,因此可以在所有文件中使用相同的声明。
内部链接性还意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因。这样,只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量。
如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用extern
关键字来覆盖默认的内部链接性:
extern const int states =50;//definition with external linkage
在这种情况下,必须在所有使用该常量的文件中使用extern关键字来声明它。这与常规外部变量不同,定义常规外部变量时,不必使用extern关键字,但在使用该变量的其他文件中必须使用extern
。然而,请记住,鉴于单个cost在多个文件之间共享,因此只有一个文件可对其进行初始化。
在函数或代码块中声明cost时,其作用域为代码块,即仅当程序执行该代码块中的代码时,该常量才是可用的。这意味着在函数或代码块中创建常量时,不必担心其名称与其他地方定义的常量发生冲突。
volatile
表示代码没有对内存单元进行修改,其值可能也会变化.这看起来很神秘,实际上并非如此。例如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)可能修改其中的内容。或者两个程序可能互相影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会变化。如果不将变量声明为volatile
,则编译器将进行这种优化:将变量声明为volatile
,相当于告诉编译器,不要进行这种优化。
mutable
它的意思为,即使结构(或类)变量为const,其某个成员也可以被修改.
struct data{
char name[30];
mutable int accesses;
}
const data veep = {...};
veep.accesses++; //allowed
内存对齐
C++11引入了两个新的关键字和来支持对内存对齐进行控制。关键字 能够获得一个与平台相关的类型的值,用于查询该平台的对齐方式。当然我们有时候并不满足于此,甚至希望自定定义结构的对齐方式,同样,C++11还引入了来重新修饰某个结构的对齐方式。