-
目录
ch1. 快速上手----------20250320
- 注释:注释的代码在程序中不起作用但未从源文件删除
更安全的方法:
#if 0
statements
#endif
- 函数声明:函数返回值类型 函数名(输入参数)
- 数组参数调用:引用,传地址调用
- 标量和常量:值传递
- 函数内部定义的变量均为局部变量,其他函数无法根据名字访问它们
- 字符串:以nul(全大写)结尾
字符串常量:"hello",占用6个字节, h e l l o 以及nul(全大写)
- printf格式代码
%d:十进制打印整形值
%o:八进制打印整形值
%x:十六进制打印整形值
%g:打印浮点值
%c:打印字符
%s:打印字符串
\n:换行
- puts函数:把指定的字符串写到标准输出并在末尾加上一个换行符
Ch3.数据-----------20250322
1.基本数据类型(4种)
整形、浮点型、指针、聚合类型
- char类型本质上是小整数型值
- 字符串常量:空字符串(“ ”)依然存在NUL字节作为终止符
使用时作为“指向字符的常量指针”
2.基本声明
- 变量声明
signed关键字一般只用于char类型,其他整型在缺省是默认有符号数,但是char类型可以是signed char或者是unsigned char
- 指针声明
int *a------------a被声明为int*类型的指针
- 隐式声明
函数为声明返回值类型,默认为整型
3.typedef
4.常量
- const
int const a;等同于 const in a;
const用于指针:看const修饰谁,离谁近
int *p;---指向整型的指针
int const *p;---指向整型常量的指针常量指针(指针的值p可以修改,指向的值*p不能改)
int *const p;---指向整型的指针常量(指针的值p不可更改,指向的值*p可以更改)
int const * const p;---指向整型常量的指针常量(指针的值和所指向的值都不能改)
指针常量(Pointer to const)与常量指针(const pointer)
可以直接从英文来区分二者
- 字符常量
- 必须使用单引号
- 只能包含一个字符
- 可以是普通字符、转义字符、或者ASCII码值对应的字符
5.作用域
- 四种类型:文件作用域、函数作用域、代码块作用域、原型作用域
- 标识符声明的位置决定作用域
文件作用域:在代码块之外声明的标识符都有文件作用域(图中声明1、2、4)
函数作用域:只适用于语句标签,语句标签用于goto语句(不太理解)
代码块作用域:花括号之间的语句
嵌套的变量共享一个内存地址
原型作用域:只适用于在函数原型中声明的参数名(声明3、8)声明5不是吗?
- 链接属性:external、internal、none
只要变量不是声明于代码块或者函数定义内部,它在缺省情况下的链接属性均为external
如果某个声明具有external链接属性,在它前面加上static关键字可以将链接属性变为internal
6.存储类型
对于代码块内部声明的变量,加上关键字static,存储类型从自动变为静态变量
修改存储类型不表示修改变量的作用域
- 静态变量:在任何代码块之外声明的变量,不属于堆栈的内存,存储于全局数据区
- 自动变量:在代码块内部声明,存储于堆栈中(程序执行离开该代码块时,自动变量自动销毁,因此在函数被反复调用时,变量的地址可能会不同)
关键字register可用于自动变量的声明,表明变量存储于硬件寄存器而不是内存中(寄存器变量访问效率比内存变量高)
内存分配:
- text段:存放程序的代码/指令,不存放变量
- data段:存放已初始化的全局变量和静态变量
- variable:不是一个标准的程序段名称
- bss段:存放未初始化的全局变量和静态变量,在程序加载时会自动初始化为0
7.static关键字
链接属性和存储类型总结
Ch4.语句
1.while语句
- while循环中使用break语句,永久终止循环
- 使用continue语句,永久终止当前的那次循环(?)
这两条语句用于嵌套循环内部,只对最内层的循环起作用
2.for语句
3.do语句
4.switch语句
- switch语句中的expression必须为整型值
switch( expression )
statement-list
statement-list:
case constant-expression
当expression的值与所有case的值不匹配时,跳转default子句
Ch5.操作符和表达式
1.++和--的前缀和后缀区别:
++i:操作数的值在修改之后返回这个值
i++:操作数的值在修改之前返回这个值
2.条件操作符:?:三参数
expression1 ? expression2 : expression3
expression1的值为真,表达式等于expression2的值
expression1的值为假,表达式等于expression3的值,且不对expression2进行求值
3.逗号操作符:
4.移位运算:
可以用%来判断某一位是否为非零
具体操作:value%2!=0
%:输出余数,只有整型数能使用
5.操作符优先级:

Ch6.指针
1.内存和地址
- 边界对齐问题:整型值存储的起始位置只能为特定的字节,通常为2或4的倍数
- 声明一个指针变量并不会自动分配任何内存,在对指针间接访问前,指针必须进行初始化
2.NULL指针
表示某个特定的指针目前并未指向任何东西
对NULL指针解引用操作是非法的
3.指针运算
非法操作:指针减去一个整数后,结果在数组的第一个元素之前
合法操作:指针加上一个整数后,结果在数组最后一个元素后面的那个内存位置(但不能对指针进行间接访问,且加法运算不能再往后加)
1. 指针+整数=?
与指针所指向的类型大小有关(注:所有指针均占用4个字节)
float占据4个字节
2. 指针-整数=?
只有当两个指针都指向同一个数组中的元素时,才允许一个指针减去另一个指针
与上一种运算有所不同,指针的差值与数据类型无关,结果是指针的距离,不需要乘上元素所占用的字节
3.关系运算
前提:指向同一个数组的元素
Ch7.函数
1.函数定义与声明
- return语句:
return语句允许从函数体的任何位置返回,并不要求一定在函数体的末尾
函数也可以没有返回值,如vois类型函数
- 函数原型(声明)
没有参数的函数原型: int *func(void)
缺省认定:程序调用一个没有原型的函数时,默认调用的函数返回一个整型的值
- 函数的参数
C语言中所有参数都是传值调用
两个规则:1.传递给函数的标量参数是传值调用
2.传递给函数的数组参数在行为上类似于传址调用
2.递归
使用字符常量比整型常量的程序可移植性更高
- 递归函数:直接或间接调用自身的函数
递归调用会保存在堆栈中的变量值,与循环不同【参数被压到堆栈中,为局部变量分配内存空间,寄存器的值必须保存】---->递归运行带来的开销,往往采用迭代实现效率会更高
3.可变参数列表(不太明白)
可变参数列表通过宏来实现,定义于stdarg.h文件中
4.函数指针
向函数传递指针:有缺陷,函数可以对指针变量进行修改,防止这样可以在函数原型中使用const关键字。
Ch8.数组
1.一维数组
- 数组名:指针常量,也是数组第一个元素的地址,类型取决于元素的类型
数组名不用指针常量表示的两种情况:
数组名作为sizeof操作符或者单目操作符&的操作数时(sizeof返回整个数组的长度而不是指向数组的指针的长度)
例子:
- 数组和指针
二者区别:1.声明数组时,根据指定的元素数量为数组保留内存空间,然后再创建数组名
2.声明指针变量时,编译器只为指针本身保留内存空间不为任何整型值分配空间
数组名参数传递给函数:
int strlen(char *string);
int strlen(char string[]);
两个声明等价,但不代表指针和数组名相等
- 数组初始化:
字符数组初始化:
2.多维数组
- 数组名:一维表示指向元素类型的指针,二维表示指向某个一维数组的指针
- 下标:
*(matrix+1)==matrix[1]
*(matrix[1]+5)==matrix[1][5]
指向二维数组的指针:二维数组第二个下标不可省略
int vetor[10], *vp=vector;
int mat[3][10], (*mp)[10]=mat;
- 多维数组在函数中传递:
一维数组函数传递:
int vet[10];
...
fun1(vet);
函数原型:
void fun1(int *v); 或者是
void fun1(int v[]);
二维数组函数传递:
int matrix[3][10];
...
fun2(matrix);
函数原型:
void fun2(int (*mat)[10]); 或者是
void fun2(int mat[][10]);
3.指针数组
定义 int *api[10];---------下标引用优先级高于间接访问
例题:
Ch9.字符串、字符和字节
1.字符串
以NUL字节结尾,为终止符号,不是字符串的一部分,字符串长度不包含NUL字节
(不太理解)
- 字符串长度:strlen( )--------返回size_t类型
- 复制字符串:
strcpy(变量,字符串常量)缺点:复制过去的字符数组长度太长会造成内存泄露
strncpy(变量s1,常量s2,size) 当s2长度大于s1长度时,结果不会以NUL字节结尾
- 连接字符串:strcat(变量 , 常量 )
strncat(变量,常量,size)
- 字符串比较:strcmp( char const *s1, char const *s2)
s1<s2 返回小于0的值, s1>s2 返回大于0的值,相等,返回0
strncmp( char const *s1, char const *s2, size)
2.字符串查找
- 查找一个字符的位置
- 查找任何几个字符
- 查找一个子串
- 字符转换
大小写转换:
返回值为大写:int toupper( in ch)
返回值为小写:int tolower( in ch)
3.内存操作
用于处理非字符串数据,因为字符串函数处理数据时遇到第一个NUL字节将停止工作
Ch10. 结构和联合
1.结构体
- 结构是一些成员的集合,各个成员可能是不同的类型
- 结构变量是标量类型
- typedef可以创建一种新的类型
结构体成员访问:点操作符----(.),从左向右结合(下标和点操作符具有相同的优先级)
如com.s------com为结构,s为成员
结构体成员间接访问:先通过指针间接访问,再使用点操作符访问成员
(*cp).f 或者cp->f
结构的自引用:非法操作
2.不完整声明
3.结构内存分配
字节对齐问题:每四个字节来分配内存,成员存储字节不够则另开4个字节
结构体成员顺序不同会造成不同的空间分配
12个字节
8个字节
sizeof和foosetof
sizeof会包含结构浪费的空间
4.传递结构和传递指针
一般来说,传递指针效率更高,且如果希望函数修改结构的任何成员,也应该使用结构传递
防止程序修改结构参数的唯一办法:像函数传递一份结构的拷贝
5.联合Union
联合的所有成员引用的是内存中相同的位置
内存占用大小:最大成员占用的字节数
联合的初始化:初始值必须是联合中第一个成员的类型,且必须位于一对花括号{}里面
Ch11.动态内存分配
数组被声明时,所需内存在编译时已经被分配,可以通过动态内存分配在运行时来分配内存
1.malloc和free函数
#函数原型
void *malloc( size_t size)
void free( void *pointer)
malloc函数:从内存池中取一块连续的内存,并返回一个指向这块内存的指针
注意:内存池空间不够时,malloc就返回一个NULL指针
void*类型的指针可以转换为其他任何类型的指针
free函数:参数要么是NULL,要么是从malloc、calloc或者realloc函数的返回值
2. calloc和realloc函数
3. 内存泄漏
分配的内存使用后不释放会造成内存泄漏
4.字节对齐问题
#include <iostream>
#pragma pack(push, 1) // 设置字节对齐为 1 字节,取消自动对齐
struct UnalignedStruct {
char a;
int b;
short c;
};
#pragma pack(pop) // 恢复默认的字节对齐设置
struct AlignedStruct {
char a; // 本来1字节,padding 3 字节
int b; // 4 字节
short c; // 本来 short 2字节,但是整体需要按照 4 字节对齐(成员对齐边界最大的是int 4)
// 所以需要padding 2
// 总共: 4 + 4 + 4
};
struct MyStruct {
double a; // 8 个字节
char b; // 本来占一个字节,但是接下来的 int 需要起始地址为4的倍数
//所以这里也会加3字节的padding
int c; // 4 个字节
// 总共: 8 + 4 + 4 = 16
};
struct MyStruct1 {
char b; // 本来1个字节 + 7个字节padding
double a; // 8 个字节
int c; // 本来 4 个字节,但是整体要按 8 字节对齐,所以 4个字节padding
// 总共: 8 + 8 + 8 = 24
};
int main() {
std::cout << "Size of unaligned struct: " << sizeof(UnalignedStruct) << std::endl;
// 输出:7
std::cout << "Size of aligned struct: " << sizeof(AlignedStruct) << std::endl;
// 输出:12,取决于编译器和平台
std::cout << "Size of aligned struct: " << sizeof(MyStruct) << std::endl;
// 输出:16,取决于编译器和平台
std::cout << "Size of aligned struct: " << sizeof(MyStruct1) << std::endl;
// 输出:24,取决于编译器和平台
return 0;
}
刷题笔记:
1.scanf:
printf: %在printf中为特殊字符,输出一个%需要用两个%
printf("%%%%")----------输出结果为%%
2.强制类型转换:int<float<double(精度排序)
int i float f double d
(int)(float)i---->将i转换为float再转换为int
(float)(int)f、(float)(double)f
从低到高可以保持值不变,从高到低会丢失精度