Welcome to C/C++ System Program’s documentation!

目录

概述简介

系统编程哲学

  • 简单代码实现复杂功能

目录

C语言篇
  • C的发展历史
  • C的优缺点
C++语言篇
  • C++的发展历史
  • C++的优缺点

语法基础

目录

c语言篇

C语言基础语法学习目标

  • 32个关键字
  • 9种控制流
  • 34种运算符
目录
程序结构

我们在学习每一种编程语言时,都会先学习写一个hello world的demo程序,下面我们将从这个小demo程序来窥探一下我们C程序的程序结构

#include <stdio.h>

int main()
{
   /* 我的第一个 C 程序 */
   int ret = 0;

   printf("Hello, World! \n");

   return ret;
}

以上是一个可以输出hello world的简单C代码,该代码的组成部分为:

  • 预处理指令#include <stdio.h>告诉C编译器在实际编译之前需要将stdio.h文件内容复制包含到当前文件
  • 函数int main()是C程序的执行入口;每个C程序有且只能有一个main函数
  • 语句&表达式return 0会终止main()函数并返回0;在C中,每个语句必须以分号结束。它表明一个逻辑实体的结束
  • 常量&变量ret是一个int型变量hello world!\n是一个字符串常量
  • 注释/*...*/将会被编译器忽略,这里放置程序的注释内容

从另外一个角度来看demo程序,该程序是由各种令牌组成:关键字标识符常量运算符

C标识符是用来标识常量名变量名函数名或任何其他用户自定义的名称。一个标识符以字母A-Za-z下划线 _ 开始,后跟0个或多个字母、下划线和数字(0-9)

需要注意的是;

  • C标识符内不允许出现标点字符,比如@$%
  • C是区分大小写的编程语言;Manpowermanpower是两个不同的标识符

C关键字就是C中的保留字,它们被赋予特殊意义。这些保留字不能作为常量名,变量名或其它标识符名称。上述demo中的includeintreturn都是C关键字。C中一共有32个关键字,详解可见C关键字

C常量可以是数值常量0;也可以是字符串常量hello world

C运算符有很多,它是构成表达式、语句、代码块的重要组成部分。详解可见C运算符

最后特别容易忽视的就是C中的空格,我们在编程中,为了规范,不同字段之间都会以空格隔开,便于编译器识别解释各字段的含义

int ret = 0;
数据类型

数据类型的本质:固定内存大小的别名

数据类型的作用:

  • 决定了定义变量的内存存储大小

数据类型的误区:

  • 它不能决定定义变量的内存存储形式
  • 它不能决定定义变量的内存存储模式

现代整个计算机科学都是建立在二进制基础上,所有数据都是以01组合形式进行存储和传输的。这里的0或1就是1bit

0x00 存储大小

在通信领域,网络带宽传输速度的基本单位是比特(bits)

然而在计算即领域,磁盘存储数据的基本单位是字节(Bytes==1字节等于8比特);不同数据类型变量的存储大小是不一样的,例如:char类型变量64位系统下的存储大小是1字节, short类型变量64位系统下的存储大小是2字节。具体相关介绍可见数据类型详解目录表

需要注意的是:

  • c语言没有规定数据类型(例如int类型)的具体大小,数据类型的具体大小和系统有关;同一系统下,同一种数据类型的大小是相同的
  • 数据类型只是一个声明,并没有占用内存空间,只有当使用数据类型定义一个变量时,变量才占用空间,其所占用空间大小由数据类型决定,数据类型声明的内存大小和变量所占的内存大小都可以使用sizeof关键字来获取
0x01 存储形式

在计算机中,我们都是以补码的形式存储并运算数据。采用补码存储的最大作用就是减法也可以通过加法器实现

  • 所有正数都是以补码的形式存放====正数的补码与其原码、反码都相同,都是正数的二进制,最高位为0
  • 所有负数都是以补码的形式存放====最高位为1保存不变,其它位都和原码相反,转换成反码;然后在反码的基础上加1,转换成补码
0x02 存储模式

既然所有正数和负数都是以补码的形式进行存储,那么此时存储方式有两种:

  • 小端模式:数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低[Unix服务器CPU ]
  • 大端模式:数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放[x86/ARM]

0x11223344的两种存储模式如下:

_images/1.jpg

需要注意的是:大端模式和小端模式只与CPU架构相关;同一CPU架构下存储模式是相同的

0x03 目录
数值型

数值型可分为两大类:

0x00 整型
类型关键字 类型说明 存储大小(linux) 值范围 示例
short 有符号短整型 sizeof(short)=2[x86和x86_64] -32768~32767 short a = 20;
unsigned short 无符号短整型 sizeof(unsigned short)=2[x86和x86_64] 0~65535 unsigned short a = 20;
int 有符号整型 sizeof(int)=2[x86]/4[x86_64] -32768~32767或-2147483648~2147483647 int a = 20[10进制]/020[8进制]/0x20[16进制];
unsigned int 无符号整型 sizeof(unsigned int)=2[x86]/4[x86_64] 0~65535或0~4294967295 unsigned int a = 20;
long 有符号长整型 sizeof(long)=4[x86]/8[x86_64] -2147483648~2147483647或‭-9223372036854775808‬~‭9223372036854775807‬ long a = 20;
unsigned long 无符号长整型 sizeof(unsigned long)=4[x86]/8[x86_64] 0~4294967295或0~‭1777777777777777777777‬ unsigned long a = 20;
long long 有符号长长整型 sizeof(long long)=8[x86和x86_64] -9223372036854775808‬~‭9223372036854775807 long long a = 20;
unsigned long long 无符号长长整型 sizeof(unsigned long long)=8[x86和x86_64] 0~‭1777777777777777777777 unsigned long long a = 20;‬
整型数 对应类型
100 int
100u unsigned int
100l long
100ul unsigned long
100ll long long
100ull unsigned long long

整型数据存在两种类型的溢出

  • 符号位溢出:超过有符号数的最大值
  • 最高位溢出:超过无符号数的最大值

注意:在所有数值类型中(包括浮点型),计算机CPU处理int类型数据的效率是最高的,int只是占用较大内存而已

0x01 浮点型
类型关键字 类型说明 存储大小(linux) 值范围 精度
float 单精度浮点数 sizeof(float)=4[x86_64] 1.2E-38~3.4E+38 6位小数
double 双精度浮点数 sizeof(double)=8[x86_64] 2.3E-308~1.7E+308 15位小数
long double 长双精度浮点数 sizeof(long double)=16[x86_64] 3.4E-4932~1.1E+4932 18位小数

在头文件float.h定义了浮点型相关的宏;通过这些宏可以输出浮点类型占用的存储空间以及它的范围值

#include <stdio.h>
#include <float.h>

int main()
{
   printf("Storage size for float : %d \n", sizeof(float));
   printf("Minimum float positive value: %E\n", FLT_MIN );
   printf("Maximum float positive value: %E\n", FLT_MAX );
   printf("Precision value: %d\n", FLT_DIG );

   return 0;
}
字符型
类型关键字 类型说明 存储大小(linux) 值范围 示例
char 有符号字符型 sizeof(short)=1[x86和x86_64] -128~127 char a = ‘a’/0x61
unsigned char 无符号字符型 sizeof(unsigned short)=1[x86和x86_64] 0~255 unsigned char a = 0xff

在C中我们可以将charunsigned char理解成1 Bytes的整型数===对照ASCII码表

  • char a = 'a'char a = 0x61int a = 97三者是等价的

注意

  • 'a':单引号表示字符
  • "a":双引号表示字符串
数组型

C语言支持数组数据结构,它的本质是:一段连续的存储相同类型变量的内存空间

所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。一个数组中的所有元素的数据类型都是相同的,都是整型浮点型字符型

数组可以分为:

一维数组

接下来我们将从以下几个方面来学习一维数组

0x00 数组定义

一维数组声明定义的格式是:type ArrayName [ArraySize];

  • type:数组元素的数据类型
  • ArrayName:数组的数组名
  • ArraySize:数组元素的数量

例如:int a[10];定义了一个名为a的数组,该数组有10个成员 每个成员都是int类型

  • 该定义开辟了sizeof(int)*10=40字节的连续内存空间,依次连续存放a[0]~a[9]10个元素
  • 数组名a是一个常量[不能当做变量被赋值],它代表数组首元素的地址
  • 此时该数组并没有被初始化,每个元素的值是随机的

定义数组时,ArraySize可以是变量,其定义方法可以是

//方法一:c99之后的编译器才支持
int i = 10;
int a[i];

//方法二:标准c写法
#define NUM 10
int a[NUM];

注意char a[10];

  • a表示数组首元素的地址,即a[0]元素的地址,a+1表示a[1]的地址
  • &a表示整个数组的首地址,&a+1表示跳过了整个数组
0x01 数组初始化

当定义一个数组时只是分配了相应的内存空间,内存空间存储的依然是之前的随机值,此时需要通过初始化存放我们想要存储的数据;数组初始化的方式有:

  • int a[4] = {1,2,3,4};:a[0]~a[3]的值分别初始化为1,2,3,4;每个元素都占用4字节空间
  • int a[4] = {1,3};:a[0]初始化为1,a[1]初始化为3,a[2]~a[3]初始为0;每个元素都占用4字节空间
  • int a[] = {1,2,3};:数组a只有3个元素,分别初始化为1,2,3;每个元素都占用4字节空间
  • int a[4] = {0};:a[0]~a[3]都初始化为0;每个元素都占用4字节空间
  • char a[4] = {0}:a[0]~a[3]都初始好为0;每个元素都占用1字节空间
  • char a[6] = "hello":a[0]~a[5]分别初始化为’h’/’e’/’l’/’l’/’o’/‘0’;每个元素占用1个字节。注意:数组元素的个数一定要大于字符串常量的长度,因为字符串是以‘0’结尾,”hello”实际是以’h’/’e’/’l’/’l’/’o’/‘0’存放的
  • char a[10] = "hello":a[0]~a[5]分别初始化为’h’/’e’/’l’/’l’/’o’/‘0’,剩余元素都初始为0;每个元素都占用1个字节空间
0x02 数组访问

数组是通过索引下标的方式来访问元素的,以下demo代码就是访问数组每个元素然后打印输出

#include <stdio.h>
int main(){
        int i;
        int a[] = {1,2,3,4,5};
        for(i=0; i < sizeof(a)/sizeof(a[0]); i++) {
                printf("%d\n",a[i]);
        }

        return 0;
}

上述demo代码中:

  • sizeof(a)表示数组a在内存中占用大小
  • sizeof(a[0])表示数组a的首元素在内存中占用大小[即每个元素在内存中占用大小]
  • sizeof(a)/sizeof(a[0])表示数组元素的个数
0x03 数组应用
冒泡排序

算法思路

  • 第一次遍历整个数组n个元素,通过相邻值比较将最大值放到最右边
  • 第二次遍历剩下的n-1个元素,通过相邻值比较将最大值放到最右边
  • 依次循环遍历,直到遍历最后1个元素

代码实现如下:

#include <stdio.h>
int main(){
        int i, j;
        int a[] = {34,5,124,624,3,65,};
        int count = sizeof(a)/sizeof(a[0]);
        for(i = 0;i < count;i++){
                for(j = 1;j < count-i;j++){
                        if(a[j] > a[j-1]){
                                int tmp = a[j-1];
                                a[j-1] =a[j];
                                a[j] = tmp;
                        }
                }
        }

        for(i=0; i < count; i++) {
                printf("%d\n",a[i]);
        }

        return 0;
}
二维数组

接下来我们同样从以下几个方面来学习一维数组

0x00 数组定义

二维数组声明定义的格式是:type ArrayName [LineNum][RowNum];

  • type:数组元素的数据类型
  • ArrayName:数组的数组名
  • LineNum:一维数组的个数,可以形象的理解为行数
  • RowNum:一维数组元素的个数,可以形象的理解为列数

例如:int a[2][10];定义了一个名为a的二维数组,该数组有2个一维数组a[0]和a[1],每个一维数组有10个成员 每个成员都是int类型,这些成员是连续存放的,逻辑上可以理解为2行10列。a[0]/a[1]分别是两个一维数组的数组名,也是两个数组首元素的地址

  • 该定义开辟了sizeof(int)*10*2=80字节的连续内存空间,依次连续存放a[0][0]~a[0][9]a[1][0]~a[1][9]20个元素
  • 二维数组名a、一维数组名a[0]a[1]是一个常量,不能当做变量被赋值
  • 此时该数组并没有被初始化,每个元素的值是随机的

注意int a[2][10];

  • a[0]代表第一行数组的首元素地址,即a[0][0]的地址,a[0]+1表示a[0][1]的地址
  • a[1]代表第二行数组的首元素地址,即a[1][0]的地址,a[1]+1表示a[1][1]的地址
  • a代表第一行数组的首地址,即a[0]的地址,a+1表示a[1]的地址
  • &a代表整个二维数组的地址,&a+1表示跳过了整个二维数组
  • a+i表示第i行数组的首地址,相当于一维数组名使用&取地址
  • *(a+i)表示第i行数组首元素的地址,相当于一维数组名
  • *(a+i)+j表示第i行第j列元素的地址,等价于&a[i][j]
  • *(*(a+i)+j)表示第i行第j列元素的值,等价于a[i][i]
0x01 数组初始化

当定义一个数组时只是分配了相应的内存空间,内存空间存储的依然是之前的随机值,此时需要通过初始化存放我们想要存储的数据;数组初始化的方式有:

  • int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};:初始化了3个一维数组a[0]~a[2]
  • int a[3][4] = {{1,2,3,4},{5,6,7,8}};:初始化了3个一维数组a[0]~a[2],a[2]所有元素都是0
  • int a[3][4] = { 0 };:初始化了3个一维数组a[0]~a[2],所有一维数组的所有元素都是0
  • int a[][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{0}};:初始化了4个一维数组a[0]~a[3]
  • char a[][10] = {"my","name","is","anony"};:初始化了4个一维数组a[0]~a[3],使用int a[][]会直接报语法错误
0x02 数组访问

二维数组同样是通过索引下标的方式来访问元素的,以下demo代码就是访问二维数组每个元素然后打印输出

#include <stdio.h>
int main(){
        int i, j;
        int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
        int line_index = sizeof(a)/sizeof(a[0]);
        int row_index = sizeof(a[0])/sizeof(a[0][0]);
        for(i=0; i < line_index; i++) {
                for(j=0; j < row_index; j++)
                {
                        printf("%d\n",a[i][j]);
                }
        }
        return 0;
}

上述demo代码中:

  • sizeof(a)表示二维数组a在内存中占用大小
  • sizeof(a[0])表示一维数组a[0]在内存中占用大小[即每个一维数组在内存中占用大小]
  • sizeof(a[0][0])表示一维数组a[0]首元素在内存中占用大小[即每个元素在内存中占用大小]
  • sizeof(a)/sizeof(a[0])表示一维数组的个数
  • sizeof(a[0])/sizeof(a[0][0])表示一维数组数组元素的个数
0x03 数组应用
冒泡排序

算法思路

  • 先将二维数组放到一个一维数组中

  • 然后对一维数组进行冒泡排序

    • 第一次遍历整个数组n个元素,通过相邻值比较将最大值放到最右边
    • 第二次遍历剩下的n-1个元素,通过相邻值比较将最大值放到最右边
    • 依次循环遍历,直到遍历最后1个元素
  • 最后将一维数组重新放回二维数组中

使用技巧
  • 可以使用二级指针指向二维数组
  • 可以使用一级数组指针指向二维数组
  • 不能使用二级指针做形参来接收二维数组【因为步长不一样】
  • 可以使用一级数组指针做形参来接收二维数组【步长可以保持一致】
  • 可以使用二维数组做形参类接收二维数组【步长可以保持一致】
多维数组

多维数组在实际工作中很难用到,最多只用到二维数组。在这里我只简单介绍一下。

例如:int a[2][4][10];定义了一个三维数组a

  • 该数组包含两个二维数组a[0]a[1]
  • 二维数组a[0]包含4个一维数组a[0][0]/a[0][1]/a[0][2]/a[0][3]
  • 二维数组a[1]包含4个一维数组a[1][0]/a[1][1]/a[1][2]/a[1][3]
  • 每个一维数组都包含10个int类型的元素

三维数组同样是通过索引下标的方式来访问元素:a[0][0][0]表示三维数组的第一个元素

三维数组的排序同样可以使用冒泡排序:先将多维数组放到一个一维数组中,然后对一维数组进行排序,最后将一维数组放回多维数组中

常规应用

接下来我们来了解一下数组的常规应用

当数组作为函数形参时,数组就会退化为指针,函数体关于数组的所有操作都是基于指针进行

0x00 应用技巧

数组问题的应用技巧:

  • 能用一维数组解决的问题不要用二维,能用二维解决的问题不要用三维,尽量避免使用多维数组
  • 定义一个数组的时候,同时将数组成员初始化为0是一个好习惯
0x01 数组作函数参数
0x02 数组作函数返回值
字符串

字符串是内存中一段连续的char空间,以'\0'结尾:例如"a"表示'a''\0'

注意:'a'表示int类型或char类型;"a"才表示字符串类型

字符串的定义初始化方法有两种

  • 字符char数组
  • 字符char指针
# 字符char数组
# 字符串的长度一定不能超过char数组的长度,因为字符串是以'\0'结尾的,必须保证char数组的最后一个元素的值是'\0'
# 所以在定义char数组时就将其初始化为0
char a[10]={‘h’,’e’,’l’,’l’,’o’};  # 初始化时,元素'o'后面的元素初始化为0即'\0',所以它也是一段连续的char空间,并以'0'结尾
char a[10]="hello";                # 初始化时,a[0]~a[4]分别被初始化'h'/'e'/'l'/'l'/'o',剩余元素初始为0
char a[10]={ 0 };                  # 初始化时,所有元素都初始为0
char a[]="hello";                  # 初始化时,数组a有6个元素a[0]~a[5],分别为'h'/'e'/'l'/'l'/'o'/'\0'

# 字符char指针
char *p = "hello";                 # 定义一个char类型的指针指向字符串

# char指针可以引用char数组
char b[10] = "hello";
char *p = b;

下面有一个demo,实现功能是:去掉给定字符串后面的空格

#include <stdio.h>

int main(){
        char a[] = "hello world a     ";
        int index=sizeof(a)/sizeof(a[0])-2;//数组a的最后一个元素是字符串结尾的‘0’,所以需要从倒数第二个元素开始遍历的
        while(a[index]){
                if(a[index] != ' '){
                        a[index+1] = 0;
                        break;
                }
                index--;
        }
        printf("%s\n",a);
}
指针型

学习C语言的指针既简单又有趣。通过指针,可以简化一些C编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。

指针也是一种数据类型,它也是固定内存大小的别名

  • 32位系统下,指针是一个4字节的无符号整数
  • 64位系统下,指针是一个8字节的无符号整数

使用指针类型定义的变量也是一种变量,叫做指针变量,它是一段可读可写的连续内存空间的别名;指针变量存储的是一个内存地址,该地址指向另一块内存地址[即另一个变量的地址]

说到内存地址,内存的最小单位是Byte,操作系统使用malloc等手动分配内存库函数分配内存的最小单位是4kB(windows下)对于内存,每个Byte都有一个唯一的编号,这个编号就是内存地址,即每个内存地址对应一个1Byte空间大小

接下来我们将从如下几个方面来学习指针

一级指针
二级指针
三级指针
指针应用
  • 能用一级指针解决的问题不要用二级,能用二级解决的问题不要用三级,尽量避免使用多级指针
结构体
枚举型
联合体
无类型
常量与变量
目录
常量
变量

变量在调用前必须声明或者定义

运算符

34种运算符

表达式
语句

9种控制流

目录
赋值语句
添加语句
循环语句
复合语句
函数

函数需要包含头文件 函数在调用前必须声明或者定义 一个C程序必须也只能有一个main函数

预处理
代码注释
关键字
关键字不需要包含头文件 32个关键字
语法规范
linux风格

变量定义

  • int dog_name
windows风格

变量定义

  • int DogName
编程习惯
  • 定义全局变量:以g开头
  • 定义结构体:以_t结尾
c++语言篇

C++是对C的继承和拓展

目录
程序结构

我们在学习每一种编程语言时,都会先学习写一个hello world的demo程序,下面我们将从这个demo程序来窥探一下我们C++程序的程序结构

//方法一
#include <iostream>
int main(void)
{
        int a=0;
        std::cout << "hello world" << std::endl;
        std::cin >> a;
        std::cout << "a=" << a <<std::endl;
        return 0;
}


//方法二
#include <iostream>
using std::cout;//声明iostream库里命令空间std中的cout
using std::endl;//声明iostream库里命令空间std中的endl
using std::cin;//声明iostream库里命令空间std中的cin
int main(void)
{
        int a=0;
        cout << "hello world" << endl;
        cin >> a;
        cout << "a=" << a <<endl;
        return 0;
}


//方法三
#include <iostream>
using namespace std;//声明iostream库里的命令空间std
int main(void)
{
        int a=0;
        cout << "hello world" << endl;
        cin >> a;
        cout << "a=" << a << endl;
        return 0;
}

上述示例中

  • std:指iostream库里的标准命令空间
  • cout:指标准输出设备
  • cin:指标准输入设备
  • endl:指换行符

以上是输出hello world和接收输入的简单C++代码,继承了C的预处理指令函数语句表达式运算符变量数据类型关键字注释等基本语法,同时也拓展了相关语法:

  • 头文件在被include包含预处理时,不需要加.h后缀
  • 声明定义变量时引入了namespace命名空间的概念,明确指定变量的作用范围
  • <<>>运算符通过运算符重载分别指定输出流方向和输入流方向

此处只是列举了C++对C的部分扩展,详细的扩展表如下:

数据类型
常量与变量
运算符
表达式
语句
函数
命名空间
  • 命令空间的本质

    • 指定标识符的作用域,实现资源的隔离
  • 命令空间的类型

    • 默认命令空间==全局作用域
    • 标准命令空间==标准作用域
    • 自定义命令空间==自定义作用域
  • 命令空间的定义

  • 命令空间的引用

常用类库

目录

c语言篇

参考文档

_images/11.jpg
_images/2.jpg
目录
标准C库
目录
字符测试
字符串操作
文件IO操作
用户管理
内存管理
系统管理
目录
日志管理
时间管理
环境变量
终端控制
进程控制
进程通信
socket管理
功能函数
目录
数学计算
数据结构和算法
系统调用
目录
文件操作
用户管理
系统管理
进程控制
进程通信
内存管理
网络管理
socket控制
自定义类库
c++语言篇

数据结构

目录

数组
链表
队列

算法基础

开发工具

目录

代码编辑
程序构建
程序调测
代码托管

参考资料

目录