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这个变量的声明,这样去解析:

  1. p: 它名叫P
  2. const p :p是常量,它不可变。
  3. *const p : p是常量指针,它不可变。
  4. int *const p :p是常量指针,它不可变,它指向一个int。
  5. 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)来组织代码,避免重复和错误。以下是关键点解释:


核心概念

  1. 问题背景
    当把一个程序拆分成多个源代码文件时,如果多个文件都需要用同一个结构(如 struct)或函数,直接在每个文件中重复声明结构会导致:
    • 重复劳动:每次修改结构都要修改所有文件
    • 易出错:可能漏改某些文件,导致不一致
  2. 解决方案:头文件
    • 头文件的作用:将需要共享的声明(如结构、函数原型)集中放在一个头文件中。
    • 其他文件通过 #include 包含头文件,无需重复写声明。

头文件应该放什么?

可以放的内容

  • 结构声明(如 struct Point { ... };
  • 函数原型(如 double calculate(Point p);
  • 符号常量(如 #define PI 3.14const 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;
   }

这样做的好处

  1. 避免重复:所有文件共享同一份声明。
  2. 易于维护:修改结构时只需改头文件。
  3. 代码复用:其他程序可以直接包含头文件和使用函数库。

补充说明

  • 内联函数和 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还引入了来重新修饰某个结构的对齐方式。

posted @ 2025-08-30 13:52  T0fV404  阅读(12)  评论(0)    收藏  举报