C++程序结构

程序编译过程

  • preprocess 预处理:都是以符号 # 开头的命令,C/C++ 一般包含三个方面的内容:宏定义、文件包含和条件编译
  • compile 编译:就是把高级语言转换为计算机可以识别的二进制语言(是由编写的源程序产生目标程序的过程)
  • link 链接

包含头文件

C 以 .h 作为扩展名,C++ 不在包含扩展名,调用库的时候让机器根据情况自动选择相应的扩展名,C++ 也支持以 C 的方式调用头文件但是不提倡

#include<stdio.h> #include<cstdio>

命名空间 namespace

  • 命名空间是解决命名冲突的机制

如果自己写的代码名字和第三方库中代码的名字相同会引起冲突无法通过编译

  • 将第三方库的代码置于特定的命名空间中解决命名冲突

调用时通过限制命名空间作为前缀解决了命名冲突

1
2
#include<iostream>
using namespace std;

实际上就是制定了调用了 iostream 标椎库中名为 std 的命名空间,所以输出输入可以省去前缀 std ,并且方便的是标准模板库的大部分操作的命名空间都是 std 作为前缀因此一般情况下小程序都是直接调用 std 命名空间

命名空间中函数的调用

通过命名空间前缀调用指定的函数:mycode::foo() 调用了名字为 mycode 下的 foo 函数

也可以引用命名空间简化调用:

如下是调用自定义的 namespaces.h 头文件下 mycode::foo()

1
2
3
4
5
6
#include "namespaces.h"
using namesapce mycode;
signed main()
{
foo();
}

这种命名空间的作用在多人合作的工程类代码中常见,一般每个人写的代码都放在一个命名空间中如此避免了小组内的命名冲突

注意:其实不能为了少些几个代码就随意引用多个 using namespace ,那样程序就又陷入的命名冲突的困境,失去了本身命名空间的作用

引用特定的对象

using 还可以只引用特定的某个对象

1
2
3
4
5
6
7
#include<iostream>
using std::cout

signed main()
{
cout<<"hello world"<<std::endl;
}

输入操作机理

要有一个概念就是从键盘键入的数据不是直接存入对应的待存入变量中,而是先存储到缓存区内,当遇到指定的字符时开始解析给待存入的变量依次赋值

  • 数值:会跳过空白字符(空格、制表符和回车符),遇到回车后开始解析数值直至遇到非法字符停止解析,即停止解析后就算缓存区还有也不会再给当前的没存入的变量赋值但是还会保留在缓存区,至于没存入的变量内容是什么由编译器而定

  • 字符串:读入字符串直至回车结束

例:若是给变量 ab 存值的时候输入 1 2 那么两个变量都会成功赋值,但若是输入的是 1b2 3那么只有 a 会成功赋值了,即使后面还有数值型变量也都会未存入

因为非法字符 b 导致后面 a变量以外的其他变量都未存入成功

a变量遇到非法字符 b停止解析赋值为 1 ,但是下一个b变量成功吃掉字符 b 所以可以继续将缓存区内的值解析,后面的变量也都赋值成功

注意:由于缓存区未利用的字符会保留的机制,对于C语言的 scanf 存入多个连续字符的时候要另外注意—— C语言存入吃回车的原因

C++ 不用注意这个问题因为 cin 从缓存区的第一个非空字符同时对于多个字符的存入时末尾回车不会存入缓存区

C++基础语法

标识符和关键字

  • 标识符和关键字不要发生冲突
  • 不同的集成环境对于标识符的限制长度不同,要做到”见名知义“

基础数据类型

C++11 引入了新的数据类型拓宽了使用范围

字符型:char、wchar_t(16位字符)、char16_tchar32_t

wchar_t、char16_t、char32_t的区别可以参考这篇博客

整型:short、int、long、long long

无符号型:unsigned int等

浮点型:float、double、long double

布尔型:bool

常量

整型常量

  • 十进制
  • 八进制:数值前面加上前缀一个前导 0
  • 十六进制:数值前面加上前缀 0x
  • 长整型:后缀加上 LL ,无符号整型后缀加上 U

浮点型常量

  • 十进制
  • 指数形式1.2e9
  • 长浮点数:后缀加上 L

修饰前缀

C++11 新规定的常量加上修饰前缀指定编码规则

  • u :unicode 16字符
  • U:unicode 32字符
  • L:宽字符
  • u8(修饰字符串):UFT-8

Unicode 和 UTF-8 有什么区别参考这篇博客

原始字符串

引号和斜杠不能直接当做字符串输出,而是必须用 \ 进行转义,为了方便需要一次性输出很多的引号和斜杠,C++11 新增了原始字符串来标识原始字符串

1
2
3
4
cout << "I print \'\\\', \"\\n\" as I like." << endl;  
//屏幕显示: I print '\', "\n" as I like.
cout << R"(I print '\', "\n" as I like.)" << endl;
//屏幕显示: I print '\', "\n" as I like.

表达式R"分隔符()分隔符"

规定:在表示字符串开头的 "( 之间可以添加其它字符作为分隔符,不过必须在表示字符串结尾的 )" 之间添加同样的字符

模式

  • 分割符左右对称,括号不能少
  • 原始字符串不会按照转义字符解码
1
2
cout << R"haha(I print '\', "\n" and )" as I like.)haha" << endl;
//屏幕显示:I print '\', "\n" and )" as I like.

命名推荐

符号常量用宏定义,宏定义不用定义数据类型,在预处理时会将所有的目标替换为定义的量在进行编译

例:#define PI 3.1415926535

命名常量用 const 定义

注意两者区别

1
2
3
4
5
6
7
8
9
10
#define P 1;
const double P=1;

signed main()
{
float f=1.0;
cout<<f+P/2<<endl;
return 0;
}
//宏定义结果为1,const定义结果为1.5

变量定义与初始化

C++11 新增了用 {} 作为初始化列表来初始化,旨在对类和对象内部不同变量和数组统一的初始化

注意:初始化和赋值是两个区别,新添的的初始化列表只能用于变量声明时的初始化,但是原先的 =() 是既可以用来初始化也可以用来赋值的。另外使用初始化列表的时候遇到失精度(窄化)会直接编译报错,而 ‘=’ 和 () 只会警告

1
2
3
int hand[]={3,4,5,6.5};//警告
int hand[4]={0};//全部初始化为0
int hand[]{2,3,4,5.0};//报错

类型转换

显示转换

1
2
3
bool someBool=(bool)someInt;
bool someBool=bool(someInt);
bool someBool=static_cast<bool>(someInt);//C++11

隐式转换

隐式转换使用不当会导致精度损失,一般编译器会给予警告

:: 运算符

遵从原则:局部优先

1
2
3
4
5
6
7
8
9
10
#include<iostream>
using namespace std;

int x=6;
signed main()
{
int x=1;
cout<x<<endl;//访问局部变量
cout<<::x<<endl;//访问全局变量
}

extern 关键字

一般在写项目的时候会分为多个 cpp 文件,那么怎么实现多个文件公用一个变量或者函数呢。extern 关键字可以用来解决此类问题

  • 引用同一个文件下变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>

int func();

int main()
{
func(); //1
extern int num;//告诉编译器存在这个变量,去其他地方找
printf("%d",num); //2
return 0;
}

int num = 3;//找到了编译通过

int func(){
printf("%d\n",num);
}
  • 引用不同文件下变量

a.cpp

1
2
3
4
5
6
7
8
#include<stdio.h>

int main()
{
extern int num;//告诉编译器存在这个变量,去其他地方找
printf("%d",num);
return 0;
}

b.cpp

1
2
3
4
5
6
7
8
#include<stdio.h>

int num = 5;//在这个文件找到了编译通过

void func()
{
printf("fun in a.c");
}

总结

  • extern 关键字只需要指明类型和变量名就行了,不能再重新赋值
  • 初始化必须在原文件所在处进行,如果不进行初始化的话,全局变量会被编译器自动初始化为 0
  • 谨记:extern 相当于声明,变量(函数)可以声明多次,但是定义只能一次

类型别名

除了之前的 typedef C++11 还规定可以用 using 来进行别名声明

1
2
typedef long long ll;
using ll=long long;

auto 类型推导

C++11 还引入了 auto,在可以推测数据类型的环境先可以使用 auto 来自动遍历容器和数组

decltype 类型推导

decltype 可以推测表达式的数据类型

1
2
3
4
doubel f();
decltype(f()) sum=0;//判断出f()返还类型为double,所以声明的sum也是double类型
int i=42;double d=3.14;
decltype(i+e) e;//推导出类型为double

基于 for 循环

可以结合使用 auto 通过引用修改数组内的元素

1
2
3
4
5
for(int i:a)//只能遍历
cout<<i<<endl;

for(auto &elem:a)//遍历引用修改
*elem=2;

函数的声明和定义

  • 函数的声明也称为函数原型或者函数签名,强调函数如何被访问,而不提供函数的实现代码,其中的形参可以只写上数据类型
  • 函数的定义是函数的具体代码实现

函数的调用

  • 第三方库的结构:提供专门的头文件,包含函数原型声明
  • 函数的调用:通常将函数的原型声明统一放在一个头文件中供程序的调用