C++ fast review

C 数据类型

基本类型:包括两种类型:整数类型和浮点类型。

整数:(不同操作系统上的大小可能不太一致)
  • char :1
  • unsigned char:1
  • signed char :1 ==char
  • 下面没有unsiged 默认为signed
    • int :4
    • short :2
    • long :8(主流64,long long也是64)
long 修饰的影响:(修饰int 的时候就等价于前面一部分的内容)
  • long long // 至少 64 位
  • long int // 与 long 完全等价
  • long long int // 与 long long 完全等价
  • signed long long // 显式带符号(默认就是带符号)
  • unsigned long long
浮点类型:(不分signed 和unsigned )

浮点类型

  • float:4, 6位小数,8位指数
  • double:8,15位小数,11位指数
  • long double :16,19位小数

枚举类型:只能赋予其一定的离散整数值的变量

void 类型

派生类型:包括:指针类型、数组类型、结构类型、共用体类型和函数类


变量

变量声明(种类上包括了定义)

变量声明向编译器保证变量以指定的类型和名称存在

  • 一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间
  • 另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。
  • 除非有extern关键字,否则都是变量的定义
    extern int i; //声明,不是定义
    int i; //声明,也是定义
    

C 中的左值(Lvalues)和右值(Rvalues)

  • 左值(lvalue): 指向内存位置的表达式被称为左值(lvalue)表达式。(左值可以出现在赋值号的左边或右边。)
  • 右值(rvalue): 术语右值(rvalue)指的是存储在内存中某些地址的数值。(右值可以出现在赋值号的右边,但不能出现在赋值号的左边)(数值型的字面值是右值

C 常量(字面量

常量的值在定义后不能进行修改。

整数常量

整数常量

前缀指定基数:
  • 0x 或 0X :十六进制,
  • 0 :八进制,
  • 不带前缀:十进制。
后缀:U 表示无符号整数(unsigned),L 表示长整数(long)

浮点常量(有符号注释明确的话就必须要有完整部分)

  • 当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。
  • 当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。

浮点常量

字符常量

  • 普通的字符(例如 ‘x’)、
  • 转义序列(不叫字符叫序列)(例如 ‘\t’),在 Windows 的命令行中,\a 通常会发出"滴"的一声。(抽象完了)

转义序列

  • 通用的字符(例如 ‘\u02C0’)

字符串常量

有两种简单的定义常量的方式:

  • 使用 #define 预处理器。
    #define LENGTH 10
    
  • 使用 const 关键字。
    const int  LENGTH = 10;
    

C 存储

可用的存储类:

  • auto(所有局部变量默认的存储类,只能用在函数内)
  • register(定义存储在寄存器中而不是 RAM 中的局部变量,最大尺寸等于寄存器的大小)(定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中
  • static(在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。)
    • 当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
    • 全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中
  • extern:
    • extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的

C 运算符

  • 算术运算符
  • 关系运算符(比较)
  • 逻辑运算符(真假)(&&)
  • 位运算符 (逐位执行操作)(A|B = 0011 1101)
  • 赋值运算符(%=)
  • 杂项运算符(指针,三元运算)
  • 优先级:

运算符优先级


C 作用域规则

  • 局部变量(在某个函数或块的内部)局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
  • 全局变量(定义在函数外部,通常是在程序的顶部)全局变量保存在内存的全局存储区中,占用静态的存储单元;
  • 形式参数(函数的参数,形式参数,被当作该函数内的局部变量)

C 数组

声明数组 // 定义 + 声明

type arrayName [ arraySize ];

初始化数组

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

传递数组给函数

void myFunction(int *param)
void myFunction(int param[10])
void myFunction(int param[])

这个指针是一个副本指针,跟形参的定义是一致的

从函数返回数组

int * myFunction()

C 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。(在表达式里写 return r; 时,编译器执行数组到指针的退化(array-to-pointer conversion),得到的是 int* 且值等于 &r[0]。)

返回数组

一些问题的汇总解读:

1. “一个有内容的指针里存的是地址值吗?”
  • 是。指针变量本身的位模式就是某个内存地址(虚拟地址)。
  • 所谓"有内容"只是这个位模式不是 0/nullptr,而指向一块可访问(希望也是合法)的内存。
2. “我定义的数组 int r[10]; 实际上也是一个地址吗?”
  • 不是。
  • r数组对象本身,它占用 10*sizeof(int) 字节的连续内存;
  • 在大多数值上下文(函数实参、赋值、算术、比较……)里,编译器会把数组名转换成"指向首元素的指针"(type 退化 int*)。
  • 这常让人误以为"数组就是指针",但它们在定义、大小、sizeof& 结果等方面完全不同:
int r[10];
std::cout << sizeof(r);   // 40 (10×4)
std::cout << sizeof(&r);  // 8  (指针大小)
3. “所以我的函数返回一个数组的时候,返回类型应该为指针对吗?”

C++ 语法根本不允许把"裸数组"写成函数返回类型:

int foo()[10];   // ❌ 直接编译失败

可选方案只有两条路:

A. 返回"指向数组的指针"或"指向首元素的指针"

B. 真正符合现代 C++ 的做法:别返回裸指针,用托管容器

std::vector<int> foo();        // 最常用
std::array<int, 10> foo();     // 大小编译期固定
结论(一句话)

“数组就是数组,指针是指针;函数想’返回数组’时,语法上只能退而求其次返回指针,但更好的做法是直接返回 std::vector / std::array,让语言帮你管内存和拷贝。”


C 指向数组的指针

double balance[50];

balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。

double *p;
double balance[10];
p = balance;

C 指针

使用指针

ip = &var;  /* 在指针变量中存储 var 的地址 */

/* 在指针变量中存储的地址 */
printf("Address stored in ip variable: %p\n", ip );

/* 使用指针访问值 */
printf("Value of *ip variable: %d\n", *ip );

C 指针的算术运算

  • 四种算术运算:++、–、+、-。
  • 假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:(跳过的是其指向的对象的大小
  • ptr++ 指针 + 1 跳过 1 个对象的大小,即增加 sizeof(*指针) 字节。

C 指针数组

可能有一种情况,我们想要让数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:

int *ptr[MAX];

可以用一个指向字符的指针数组来存储一个字符串列表(见字符串)


C 指向指针的指针

指向指针的指针

  • 当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符
  • printf("Value available at **pptr = %d\n", **pptr);

C 传递指针给函数

只需要简单地声明函数参数为指针类型即可

void getSeconds(unsigned long *par);
unsigned long sec;
getSeconds( &sec );

也能接受数组作为参数

double getAverage(int *arr, int size);
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;

当我们需要传递结构体,或者其他较大数据类型时,使用地址传值,这样形参就不会拷贝和原数据一样大的空间了,当然,若是不想改变原地址的数据,则用const修饰

  • “用 const 修饰"并不是"复制一份”,而是在编译期加一把"只读"锁
void foo(const int *p){
    *p = 42;        // ❌ error: assignment of read-only location '* p'
}

C 函数指针

函数指针是指向函数的指针变量。

typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型

int max(int x, int y){return x > y ? x : y;}

int main(void){
    /* p 是函数指针 */
    int (* p)(int, int) = & max; // &可以省略
}

回调函数

函数指针作为某个函数的参数

回调函数

反向控制,框架变平台
  • 库只负责通用流程,把"差异化"代码挖成空壳回调;用户填回调即可,无需改库源码,实现"好莱坞原则"——Don’t call us, we’ll call you。

回调让"流程"与"策略"分离,库做平台,用户做插件,系统更灵活、更松耦、更异步。


C 字符串

实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。(null字符就是’\0’)复习一下:

字符串

操作函数汇总:(先第一个参数,再第二个参数)

字符串函数

  • strcpy(s1,s2)复制 s2 到s1
  • strcat(s1,s2) 连接 s2 到 s1的解位
  • strlen(s1) 返回s1的长度
  • srrcmp(s1,s2)比较s1 和s2的大小
  • strchr(s1, ch):返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
  • strstr(s1, s2):返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

C 结构体

定义结构

struct tag {
    member-list
    member-list
    member-list
} variable-list

tag 是结构体标签。member-list 是标准的变量定义,variable-list 结构变量(相当于变量名称),定义在结构的末尾。

struct Point {
    int x;
    int y;
} p1, p2 = {3, 4};   /* p1 未初始化,p2 初始化为 (3,4) */

struct Point p3;

结构体变量的初始化

  • 可以在定义时指定初始值,见上面的内容。
  • 也可以:struct Point p3=...

访问结构成员

成员访问运算符(.) Book1.title

结构作为函数参数

void printBook( struct Books book );

(需要指明是结构题)

指向结构的指针

  • 指针变量中存储结构变量的地址:struct_pointer = &Book1
  • 指向该结构的指针访问结构的成员:struct_pointer->title;

位域

  • 有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。
  • 所谓"位域"是把一个字节中的二进位划分为几个不同的区域
  • 定义与结构定义相仿:
struct 位域结构名
{
    位域列表
};

例如:类型说明符 位域名: 位域长度 (类型说明符说明的是这个struct的总大小

struct bs{
    int a:8;
    int b:2;
    int c:6;
}data;
运算:
pbit->a=0;    /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3;    /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */

C 共用体

在相同的内存位置存储不同的数据类型(意味着会相互覆盖)

union [union tag]{
    member definition;
    member definition;
    member definition;
} [one or more union variables];
  • 共用体占用的内存应足够存储共用体中最大的成员。
  • 访问共用体的成员通结构体

C 位域(字节存储,如果在赋值的时候超出,直接变为0)

struct{type [member_name] : width ;};

C typedef

使用它来为类型取一个新的名字:typedef unsigned char BYTE;

typedef vs #define

  • #define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
  • typedef 仅限于为类型定义符号名称#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

C 输入 & 输出

C 语言中的 I/O (输入/输出) 通常使用 printf() 和 scanf() 两个函数。

  • scanf() 函数用于从标准输入(键盘)读取并格式化,
  • printf() 函数发送格式化输出到标准输出(屏幕)。
  • #include <stdio.h> // 执行 printf() 函数需要该库

printf()

  • %d 格式化输出整数
  • %f 格式化输出浮点型数据:想"指定位数"就在 % 和 f 之间插入 .n,其中 n 是你想要的小数位数(可 0 ~ 任意)。

C 文件读写

  • 非二进制:open get put close
  • 二进制:fread,fwrite
  • FILE *fopen( const char * filename, const char * mode );(变量的类型是指针)

文件打开模式

关闭文件

int fclose( FILE *fp );

写入文件

int fputc( int c, FILE *fp );
int fputs( const char *s, FILE *fp );

例:

写入文件示例

二进制 I/O 函数

二进制IO函数


C 预处理器

  • C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。是一个文本替换工具
  • 所有的预处理器命令都是以井号(#)开头。

预处理器指令


C 头文件

头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义:常量、宏、系统全局变量和函数原型

头文件


C 强制类型转换

(type_name) expression

C 错误处理

  • perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
  • strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。

C 可变参数

int func(int, ... ){......}
  • func() 最后一个参数写成省略号,即三个点号(

使用:

  1. 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
  2. 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  3. 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  4. 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  5. 使用宏 va_end 来清理赋予 va_list 变量的内存。

例:

可变参数示例


C 内存管理

这些函数可以在 <stdlib.h> 头文件中找到(注意初始化与不初始化)

内存管理函数


C 命令行参数

  • 从命令行传值给 C 程序。这些值被称为命令行参数
  • int main( int argc, char *argv[] ) argv[1](好像没有argv[0])(0的位置是程序自动填入的,如:argv[0] “./hello.exe”)
  • 如何输入:$./a.out testing1 testing2
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐