- 字符转 ASCII 码
char c = 'A';
printf("%c 的 ASCII 码为: %d\n", c, c);
- 动态可变长的结构体
typedef struct
{
int id;
char name[0];
}stu_t;
定义该结构体,只占用4字节的内存,name不占用内存。
stu_t *s = NULL; //定义一个结构体指针
s = malloc(sizeof(*s) + 100);//sizeof(*s)获取的是4,但加上了100,4字节给id成员使用,100字节是属于name成员的
s->id = 1010;
strcpy(s->name,"hello");
注意:一个结构体中只能有一个可变长的成员,并且该成员必须是最后一个成员。
- 内存管理
malloc() 函数:用于动态分配内存。它接受一个参数,即需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。
calloc() 函数:用于动态分配内存,并将其初始化为零。它接受两个参数,即需要分配的内存块数和每个内存块的大小(以字节为单位),并返回一个指向分配内存的指针。
realloc() 函数:用于重新分配内存。它接受两个参数,即一个先前分配的指针和一个新的内存大小,然后尝试重新调整先前分配的内存块的大小。如果调整成功,它将返回一个指向重新分配内存的指针,否则返回一个空指针。
- 编译 & 执行 C 程序
1、新建 hello.c 文件,编写代码
2、键入 gcc hello.c,输入回车,编译代码。
3、如果代码中没有错误,命令提示符会跳到下一行,并生成 hello.out(Windows 生成 hello.exe) 可执行文件
4、键入 .\hello.exe 来执行程序
- 编译多个 c 文件
$ gcc test1.c test2.c -o main.out
$ ./main.out
gcc 命令如果不指定目标文件名时默认生成的可执行文件名为 a.out(linux) 或 a.exe(windows) 。用 gcc [源文件名] -o [目标文件名] 来指定目标文件路径及文件名。
- 因编译器的原因,生成的 .exe 文件打开时会一闪而过,从而观察不到其运行的结果,这是因为 main() 函数结束时,DOS 窗口会自动关闭。为了避免这个问题可在 return 0; 前加入 system("pause"); 语句。
#include <stdio.h>
#include <stdlib.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
system("pause"); //暂停函数,请按任意键继续...
return 0;
}
- 自动类型转换规则
- 1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
- **
2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
- ** 3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
- ** 4、char型和short型(在visual c++等环境下)参与运算时,必须先转换成int型。
- ** 5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分直接舍去。
- 变量不初始化
在 C 语言中,如果变量没有显式初始化,那么它的默认值将取决于该变量的类型和其所在的作用域。
对于全局变量和静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。
以下是不同类型的变量在没有显式初始化时的默认值:
- 整型变量(int、short、long等):默认值为0。
- 浮点型变量(float、double等):默认值为0.0。
- 字符型变量(char):默认值为'\0',即空字符。
- 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。
- 数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。
需要注意的是,局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。
总结起来,C 语言中变量的默认值取决于其类型和作用域。全局变量和静态变量的默认值为 0,字符型变量的默认值为 \0,指针变量的默认值为 NULL,而局部变量没有默认值,其初始值是未定义的。
- C 中的变量声明 变量的声明有两种情况:
1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。 2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。
除非有 extern 关键字,否则都是变量的定义。
extern int i; //声明,不是定义
int i; //声明,也是定义
- 内存寻址由大到小,优先分配内存地址比较大的字节给变量,所以说变量越先定义,内存地址就越大。 如下面代码,先定义变量 a,再定义变量 b,打印出 a 的地址是 0x7fff5fbff828,b 的值是 0x7fff5fbff824。a 的地址比 b 的地址大 4 字节。
int a = 10;
int b = 20;
printf("a = %d, b = %d, pa = %p, pb = %p\n", a, b, &a,&b); // 输出变量的地址
- 变量应先定义再赋值,在一个表达式中,左值必须是变量,右值可以是变量,常量或者表达式, 以下输出结果 是 3
int a,b;
a=(b=3);//注意左值 等同a=b=3,但是a=(a=b)=3是错误表示
printf("%d\n",a);//输出 3
首先,表达式的最右侧 `b = 3` 执行。这意味着将整数 `3` 赋值给变量 `b`。此时,变量 `b` 的值变为 `3`。
然后,整个赋值表达式 `b = 3` 的结果(即 `3`)被赋值给变量 `a`。这意味着变量 `a` 也将获得值 `3`。
- 整数常量
整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
85 /* 十进制 */
0213 /* 八进制 */
0x4b /* 十六进制 */
30 /* 整数 */
30u /* 无符号整数 */
30l /* 长整数 */
30ul /* 无符号长整数 */
int myInt = 10;
long myLong = 100000L;
unsigned int myUnsignedInt = 10U;
- const 关键字
const int var ; //error
const int var2;
var2 = 5; //error
const int var3 = 5; //ok
x
- #define 与 const 区别
- 不使用三方变量交换 两个 int 的值
void swap_test() {
int a = 10;
int b = 20;
printf("before swap a = %d, b = %d\n", a, b);
//a 的新值是 10 XOR 20,这个操作将 a 存储了 10 和 20 的位的异或结果。
a = a ^ b;
//新的 b 相当于 (a XOR b) XOR b。根据异或的特性,由于 b ^ b = 0,我们可以得到:b 现在是原始 a 的值,即 10
b = a ^ b;
//新的 a 相当于 (a XOR b) XOR a,由于 a ^ a = 0,所以得到 a = b;
a = a ^ b;
printf("after swap a = %d, b = %d\n", a, b);
}
- 取余说明
取余,也就是求余数,使用的运算符是 %。C 语言中的取余运算只能针对整数,也就是说,% 的两边都必须是整数,不能出现小数,否则编译器会报错。另外,余数可以是正数也可以是负数,由 % 左边的整数决定:- ** 如果 % 左边是正数,那么余数也是正数;** 如果 % 左边是负数,那么余数也是负数;
- 内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加 static
- 关于 main 函数的参数
- 参数传递 本质上说,C 里面所有的函数参数传递,都是值传递。
指针传递之所以能改变传递参数变量的值,是因为 swap 函数交换的不是传递进来的指针本身,而是指针指向的值。
- 可以用预处理命令 define 来定义简单函数:
#define MAX_3(a, b, c) ( ((a > b ? a : b) > c) ? (a > b ? a : b) : c )
#define MIN_3(a, b, c) ( ((a < b ? a : b) < c) ? (a < b ? a : b) : c )
#define MAX_2(x, y) ( x> y ? x : y )
#define MIN_2(x, y) ( x< y ? x : y )
#define ARR_SIZE(a) ( sizeof( (a) ) / sizeof( (a[0]) ) )
#define MULTIPLE(m, n) ( (m%n == 0)?0:1 )
#define AVE_3(a, b, c) (a+b+c)/3
#define SUM_3(a, b, c) a+b+c
#define SWAP(a, b){int t= a;a=b;b=t;}
printf("MAX_3(10,12,1):%d\n",MAX_3(10,12,1));
printf("MAX_3(10,12.1,1):%.2f\n",MAX_3(10.1,12,1));
- 初始化局部变量和全局变量 当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化,如下所示:
预处理器
- 字符串常量化运算符(#)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。
#define message_for(a,b )\
printf(#a " and " #b ": are friends")
message_for(lily,lucy);
- 标记粘贴运算符
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
void mark_paster_identifier() {
int token34 = 40;
tokenpaster(34);
}
//输出:
token34 is 40
- File 操作文件模式
注意: 只有用 r+ 模式打开文件才能插入内容,w 或 w+ 模式都会清空掉原来文件的内容再来写,a 或 a+ 模式即总会在文件最尾添加内容,哪怕用 fseek() 移动了文件指针位置。
- 格式化字符串
char content[100];
int randomNum = rand()%100 + 1;
sprintf(content, "Hi Jerry,How are you! money:%d", randomNum);
printf("%s",content);
- 输入输出
- 如何安全的进行 strcpy 拷贝
typedef struct Books {
char title[5];
char author[50];
int price;
} Book;
strncpy(book.title, "C Programming",sizeof(book.title) -1);
// -1 确保留出一个位置给结束符
book.title[sizeof(book.title) - 1] = '\0'; // 显式添加结束符
-
.c C语言源文件 .h: 头文件(header)
-
C 语言打印 C 中打印输出使用 printf 时,需要使用 #include <stdio.h> studio.h 代表头文件, 其中 stdio: standard inout output
-
数据类型
char 1 8位 最大值 256
short 2 16 位 最大值 65535
int 4 32 位
long 4/8 32/64 位
long long 4/8
float 4
double 8
存在这么多类型的原因是为了更加丰富地、恰当地(节省性能)地表达生活中的各种值,例如 表达年龄的数据类型 就不应该用 int,而应该用 short
- 计算机中数据单位
bit byte kb mb gb tb pb
-
全局变量和局部变量 局部变量的作用域是变量所在的局部范围 全局变量的作用于是整个工程
-
常量
字面常量: 直观写出来的常量,例如直接写个 3,就属于字面常量 const 修饰的常变量 #define 定义的标识符常量, 例如 #define MAX 10, 注意:不要写成 #define MAX = 10 枚举常量:
enum SEX { MALE, FEMALE, SECRET };
- 字符串
“hello” 这种又上引号引起来的一串字符串称为字符串字面值,或者简称字符串。 注:字符串的结束标记是一个 \0 的转义符,在计算字符串长度的时候 \0是结束标志,不算做字符串内容
- ASCII 码表
- sizeOf 和 strlen 返回的都是 size_t 类型,打印的时候,需要用 %zu 占位,不能用 %d 占位
char arr1[] = "Hello";
printf("arr1:%s, length:%zu\n", arr1, strlen(arr1)); // 输出 hello
- 数组
int n = 10
int arr[n] = {1,2,3,4} //这种方式是错误的,数组的大小必须是常量值,这里 n 是变量值
-
强制类型转换
-
有符号数 规定最高位放符号位,最高位为 1: 代表正数 最高位为 0 代表负数
-
原码、补码、反码
只要是整数(不管是整数还是负数),内存中存储的都是二进制的补码
对于正数来说, 原码、补码、反码相同
负数: 原码、补码、反码是不一样的
以 -2 为例:
原码:10000000 00000000 00000000 00000010
| 原码的符号位不变,其他位按位取反
反码:11111111 11111111 11111111 11111101
| 反码 + 1
补码:11111111 11111111 11111111 11111110
- 常见关键字
auto: 修饰局部变量,例如 auto int a = 10,但是 auto 是可以省略的,因此基本不用 goto: register:寄存器, 默认 int a = 10, a是在内存里面的, 想要 a访问更快,加上 register,建议编译器把 a 定义成定义寄存器变量(计算机寄存器数量有限只有几十个,因此不能保证一定会放到寄存器,仅仅只是建议而已) signed: 有符号数,其实叫 signed int ,等价于 int unsigned:无符号数,永远都是正数 static: 修饰局部变量:局部变量的生命周期变长 修饰全局变量: 改变了变量的作用域,让静态的全局变量智能在自己所在的源文件内部使用,除了源文件就没法使用了 修饰函数: 改变了函数的作用域(不准确),改变了函数的链接属性, 外部链接属性 -> 内部链接属性
struct: 结构体关键字 typedof: 类型定义 -类型重定义,别名, 例如定义
unsigned int age = 20;
//给 unsigned int 取一个别名叫 u_int, 后面就可以直接用 u_int 替代unsigned int //的声明了
typedef unsigned int u_int;
u_int age2 = 20;
union: 联合体/共用体 volatile:
- 计算器存储数据
从下往上: 访问速度越来越快,空间越来越小,造价越来越高
- 指针
int a = 10;
&a : 取出 a 的地址
//地址也可以用变量存储,p 存放 a 的地址, p称为指针变量,类型位 int*
int* p = &a
//通过地址找到 a: 采用 *p : p 前面的 * 代表解引用操作符, 下面的代码输出 10
printf("%p", *p)
//把 a这个地址所在的值改为 20
*p = 20
//输出 20
printf("%d", a)
指针的名字叫 p,类型是 int*
- 使用不同的方式输入同一个变量值不一样(待确定原因)
short s1 = 55;
//分别输出 55 和 5.5 ,原因还不知道
printf("%d, %f\n", s1, s1);
- 在方法外部获取数组长度,和在方法内部获取 arr size 长度不一致
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(arr)/sizeof(arr[0]);
binearySearch(arr,size, 7);
void binearySearch(int arr[],int size, int target)
{
int size2 = sizeof(arr) / sizeof(arr[0]);
printf("binearySearch sizeof(arr):%zu, sizeof(arr[0]:%zu, size2:%zu\n", sizeof(arr),sizeof(arr[0]),size2);
}
binearySearch 外部获取到的 arr size 为 10,在 binearySearch内部获取到的 size 为 2
当 `arr` 是一个数组并且在函数 `binearySearch` 中使用 `sizeof(arr)` 时,你会遇到一个常见的陷阱。在函数内部,`sizeof(arr)` 并不返回数组的大小,而是返回传递给函数的参数 `arr` 的大小,这实际上是一个指针的大小,而不是原始数组的大小。
当你在函数外部声明 `int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};` 时,`sizeof(arr)` 将返回整个数组的大小,即 10 个 `int` 的大小。但是,当你将 `arr` 作为参数传递给函数时,它被退化为指向数组首元素的指针。因此,在函数内部 `sizeof(arr)` 将返回指针的大小,这通常是一个 `unsigned long` 类型,而不是数组的大小。
这就是为什么在 `binearySearch` 函数内部 `size2` 被获取为 2 的原因,这个值实际上是 `sizeof(int*) / sizeof(int)` 的结果,而不是数组的大小。在 32 位系统上,指针的大小可能是 4 字节,而在 64 位系统上,指针的大小是 8 字节。`sizeof(int)` 通常是 4 字节,所以 `sizeof(arr) / sizeof(arr[0])` 的结果可能是 2(在 64 位系统上)。
要解决这个问题,你应该避免在函数内部使用 `sizeof(arr)` 来获取数组的大小。相反,你应该将数组的大小作为参数传递给函数,就像你已经做的那样。你的 `binearySearch` 函数已经正确地将 `size` 作为参数,所以你应该使用这个参数,而不是重新计算数组的大小。
- 字符串相关 api
获取字符串长度:
char arr1[] = "Welcome to C++";
//获取字符串长度
int len = strlen(arr1);
比较两个字符串是否相等(区分大小写):strcmp 比较两个字符串是否相等(不区分大小写):strncasecmp 比较两个字符串的前N位是否相等(不区分大小写):strncasecmp
- 生成随机数
其值的范围从 0 到 RAND_MAX。RAND_MAX 是 <stdlib.h> 头文件中定义的一个宏,它表示 rand 函数所能生成的最大随机数。
RAND_MAX 的具体值依赖于实现,但标准规定它至少是 32767(即 2^15 - 1
rand()
- 数据存放区域
- size_t 类型
size_t 是一种数据类型,近似于无符号整型,但容量范围一般大于 int 和 unsigned。这里使用 size_t 是为了保证 arraysize 变量能够有足够大的容量来储存可能大的数组。size_t 类型在C语言标准库函数原型使用的很多,数值范围一般是要大于int和unsigned.
但凡不涉及负值范围的表示size取值的,都可以用size_t;比如array[size_t]。
size_t 在stddef.h头文件中定义。