C语言基础知识
文章目录
extern 关键字使用
可声明变量和函数。
-
在单个文件内,类似普通声明
-
在别的
.c
文件内定义,在本.c
文件内直接使用,别的文件要包含<stdio.h>origin.c
#include <stdio.h> int num = 0;
user.c
#include <stdio.h> extern int num;
-
在别的头文件
.h
内,在其对应.c
文件内定义。origin.h
extern int num
origin.c
#include <stdio.h> int num = 0;
user.c
#include <stdio.h> #include "origin.h" extern int num;
其他存储类关键字:
auto:定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。
static\\register\\
变量声明:
-
在函数或块内部的局部变量
-
在所有函数外部的全局变量
-
在形式参数的函数参数定义中:如
#include <stdio.h> int main() { int a=0, b=1;int sum(int, int);int c = sum(a, b);return (0); } int sum(int a, int b) {return a+b; }
变量默认初始化:
int->0;char->‘\\0’;float->0;double->0;pointer->NULL(0x0)
数组
函数指针
对于一个定义好的函数名,如
#include <stdio.h>
int sumInt(int a, int b)
{return a + b;
}
- 直接使用函数名
int main()
{printf("sumInt address is %p\\n", sumInt);printf("&sumInt address is %p\\n", &sumInt);printf("*sumInt address is %p\\n", *sumInt);
}
结果为:
sumInt address is 00459D5B
&sumInt address is 00459D5B
*sumInt address is 00459D5B
说明func_name==&func_name==*func_name
- 使用函数指针
int main()
{int(*f)(int, int);f = &sumInt;printf("sumInt address is %p\\n", sumInt);printf("f address is %p\\n", f);printf("&f address is %p\\n", &f);printf("*f address is %p\\n", *f);
}
结果为:
sumInt address is 000A9D5B
f address is 000A9D5B
&f address is 0136FE88
*f address is 000A9D5B
说明:函数指针与函数名存储同样的地址,指向函数名即f->func_name,即有如下关系
指针
左值与右值、指针、*、&
枚举
在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的
enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
}
C风格字符串
大坑: 异同
char* s = "abc";
与char s[]="abc";
与char *s = new char[3];s="abc"
不要将一个字符串直接传递给一个函数或者赋值给一个char*类型的指针,除非你知道这个字符串中的字符的内容不会被改变
如何你可能会修改这个字符串的值,请使用char[],如果想使用字符串不变量,请使用const char*
小坑:
C 语言的字符串函数只针对单字节字符有效,对于多字节字符都会失效,比如
strlen()
、strtok()
、strchr()
、strspn()
、toupper()
、tolower()
、isalpha()
等不会得到正确结果。
比如:在vs 编译器下:
char* s = u8"中文";
printf("%d\\n", strlen(s)); // 结果为6不是2
char* s1 = "中文";
printf("%d\\n", strlen(s1)); // 结果为4不是2
sizeof\\strlen
char size[7] = { 'A', 'B', 'C', '\\0' };
char size2[7] = "ABC";
printf("%s, size: %d, strlen: %d\\n", size, sizeof(size), strlen(size));
printf("%s, size: %d, strlen: %d\\n", size2, sizeof(size2), strlen(size2));
结果为:
ABC, size: 7, strlen: 3
ABC, size: 7, strlen: 3
sizeof统计数组所占内存大小,strlen统计非空字符串长度
打印地址:
char size[7] = { 'A', 'B', 'C', '\\0' };
printf("size: %p, size: %s, &size: %p, *size: %c\\n", size, size, &size, *size);
for(int i=0; i<strlen(size);++i){printf("char: %c, size+%d: %p, &size[%d]: %p\\n", size[i], i, size + i, i, &size[i]);
}
printf("char: %c, size+%d: %p, &size[%d]: %p\\n",size[3], 3, size + 3, 3, &size[3]);
printf("char: %c, size+%d: %p, &size[%d]: %p\\n",size[4], 4, size + 4, 4, &size[4]);
结果为:
size: 00D3FA48, size: ABC, &size: 00D3FA48, *size: A
char: A, size+0: 00D3FA48, &size[0]: 00D3FA48
char: B, size+1: 00D3FA49, &size[1]: 00D3FA49
char: C, size+2: 00D3FA4A, &size[2]: 00D3FA4A
char: , size+3: 00D3FA4B, &size[3]: 00D3FA4B
char: , size+4: 00D3FA4C, &size[4]: 00D3FA4C
- size+i = &size[i]
- size = &size
坑:
- 不能
char* s="123"
初始化char*:深拷贝:可以realloc()
char* s1 = (char*)malloc(1 * sizeof(char)); // 必须分配空间,不能为NULL
char s2[] ="123456789";
strcpy_s(s1, strlen(s2) + 1, s2); //要分配包含空字符的存储空间
//或者
// memcpy(s1, s2, strlen(s2) + 1);
//或者
// memmove(s1, s2, strlen(s2) + 1);
s1[4] = 'a';
printf("s %p, s2 %p \\n", s1, s2);
printf("s1 %s, s2 %s", s1, s2);
s 01558040, s2 00FFFE54
s1 1234a6789, s2 123456789
或者:浅拷贝——char* 指针指向的是一个char[] 的地址,不能realloc()
char* s1 = (char*)malloc(1 * sizeof(char));
char s2[] ="123456789";
s1 = s2;
s1[4] = 'a';
printf("s %p, s2 %p \\n", s1, s2);
printf("s1 %s, s2 %s", s1, s2);
s 012FFB5C, s2 012FFB5C
s1 1234a6789, s2 1234a6789
或者:
char* s1 = (char*)malloc(1 * sizeof(char));
const char* s3 = "123";
s1 = const_cast<char*>(s3);
// s1[1] = 'a'; //不允许改变
printf("s1 %p, s3 %p \\n", s1, s3);
printf("s1 %s, s3 %s", s1, s3);
s1 00EC61DC, s3 00EC61DC
s1 123, s3 123
- memcpy() 函数:用于从源内存区域复制数据到目标内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
- memmove() 函数:类似于 memcpy() 函数,但它可以处理重叠的内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
操作C风格字符串的函数:
- strcpy
- strcat
- strlen
- strcmp
- strchr
- strstr
结构体
定义:struct struct_name { member-list}
声明:struct struct_name var
访问结构体成员:.
初始化:struct struct_name var = {membe_vals}
结构体指针:struct struct_name * var_ptr
内存对齐:
公用体
union
声明与定义:基本同结构体
共用体占用的内存应足够存储共用体中最大的成员
位域
运算符为:
。有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。用于数据结构中多包含布尔型变量的场景
- 最少占用4个字节大小的内存,少于会自动补全;同时也会执行内存对齐原则。
- 位域上的&会被忽略
- 如果超出范围会被截断
vs studio查看内存:
- 点击->调试->窗口->内存
- 然后右键要监视的结构体变量
- 在地址栏输入结构体变量地址或者&结构体名取值
右键更改显示方式和列等:
示例1:
可以发现book2第一个字节存储的内容为253,二进制为1111,1101。存储结构如下图:
- 对于vs/vc编译器,不会把不同类型的位域进行合并,gcc则会合并,见这里
typedef
typedef与#define比较
- #define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
预处理器
见C 预处理器
宏定义
#define
#ifndef\\#ifdef
#if\\#else
#endef
#endif
- 参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格。
- 有些编译器会对宏定义的作用域作了扩展,即不管宏定义在哪里开始,其作用域都是整个文件
- #、##、
- 宏不可以递归地展开:如果预处理器在 A 宏的替换文本中又遇到了 A 宏的名称,或者从嵌套在 A 宏内的 B 宏内又遇到了 A 宏的名称,那么 A 宏的名称就会无法展开。
#undef:取消宏定义
预定义宏
__DATE__
等
预定义的标识符__func__
可以在任一函数中使用
#ifndef HEADER_FILE
#define HEADER_FILEthe entire header file file#endif
其他
#error:当遇到标准错误时,输出错误消息
#pragma:使用标准化方法,向编译器发布特殊的命令到编译器中
C可变参数
头文件: stdarg.h
运算符:...
具体步骤如下:
- 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
- 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
- 使用 int 参数和 va_start() 宏来初始化 va_list 变量为一个参数列表。宏 va_start() 是在 stdarg.h 头文件中定义的。
- 使用 va_arg() 宏和 va_list 变量来访问参数列表中的每个项。
- 使用宏 va_end() 来清理赋予 va_list 变量的内存。
常用的宏有:
va_start(ap, last_arg)
:初始化可变参数列表。ap
是一个va_list
类型的变量,last_arg
是最后一个固定参数的名称(也就是可变参数列表之前的参数)。该宏将ap
指向可变参数列表中的第一个参数。va_arg(ap, type)
:获取可变参数列表中的下一个参数。ap
是一个va_list
类型的变量,type
是下一个参数的类型。该宏返回类型为type
的值,并将ap
指向下一个参数。va_end(ap)
:结束可变参数列表的访问。ap
是一个va_list
类型的变量。该宏将ap
置为NULL
。
变参宏无法智能识别可变参数的数目和类型,因此实现变参函数时需自行判断可变参数的数目和类型。所以我们就要想一些办法,比如
- 显式提供变参数目或设定遍历结束条件
- 显式提供变参类型枚举值,或在固定参数中包含足够的类型信息(如printf函数通过分析format字符串即可确定各变参类型)
- 主调函数和被调函数可约定变参的数目和类型
va_arg(ap, type)
宏中的 type 不可指定为以下类型:
- char
- short
- float
内存管理
calloc
free
malloc
realloc
- memcpy() 函数:用于从源内存区域复制数据到目标内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
- memmove() 函数:类似于 memcpy() 函数,但它可以处理重叠的内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
详见C风格字符串
命令行参数
./hello arg1 arg2
hello.c
int main(int argc, char* argv[])
{for(int i=0; argv[i]!=NULL; i++){printf("argv %d is: %s\\n", i, argv[i]);}
}
结果为
argv 0 is: ./hello
argv 1 is: arg1
argv 2 is: arg2
第一个参数argc表示参数个数,第二个参数argv表示参数
argc=为1 + 参数个数;
argv第一个参数为函数名称./hello
常用头文件及库
<assert.h>
<ctype.h>
- isalnum:该函数检查所传的字符是否是字母和数字
- isalpha\\isdigit:
- islower\\isupper| tolower\\toupper
- isspace\\iscntrl\\isprint
<errno.h>
extern int errno;
<float.h>
FLT_MAX
\\FLT_MIN
<limits.h>
各种类型的范围大小限制如:
CHAR_MAX
\\CHAR_MIN
INT_MAX
\\INT_MIN
等
<locale.h>
日期格式和货币符号
<math.h>
double pow(double, double)
double exp(double)
double sqrt(doubke)
double ceil(double)
double fabs(double)
double floor(double)
double fmod(double, double)
<setjmp.h>
int setjmp(jmp_buf environment);
void longjmp(jmp_buf environment, int value);
<stdlib.h>
double atof(const char* str);
int atoi(const char* str);
long atol(const char* str);
其他:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
void *bsearch(const void *key, const void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *))
int abs(int);
long labs(long);
int rand(); // 0 - RAND_MAX之间的伪随机数
void srand(unsigned int seed);
<string.h>
void* memchr(const void*, int, size_t);
//在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置。
<time.h>
输入输出
基本的对象为char*、char[]、char以及FILE文件流包括(标准输入流stdin,标准输出流stdout)等。
相互之间的转换关系如下图所示:
printf\\sprinf_s\\fprintf
scanf_s\\prinf
fprintf(stdout, "%d %d %d\\n", i, j, k);
等价于
printf("%d %d %d\\n", i, j, k);
等价于
char* s = (char*)malloc(20 * sizeof(char));
sprintf_s(s, 20, "%d %d %d\\n", i, j, k);
printf("%s", s);
等价于
char buf[20];
sprintf_s(buf, sizeof(buf), "%d %d %d\\n", i, j, k);
printf("%s", buf);
等价于
char buf[20];
sprintf_s(buf, sizeof(buf), "%d %d %d", i, j, k);
puts(buf); //自动添加\\n
例子:
sprinf_s自动在buf后面添加’\\0’
char buf[4];
int real = sprintf_s(buf, sizeof(buf), "%d", 522);
printf("buf: %s, real: %d", buf, real);
buf: 522, real: 3
scanf_s| fscanf
char s[5];
scanf_s("%s", s, 5);// 最多四位,超出5位s中没有任何字符
int sscanf(const char* s, const char* format, ...);
gets\\puts| getc\\putc| getchar\\putchar
gets、gets_s(char*, size_t)\\puts(char*):
char buf[20];
int n = gets_s(buf, sizeof(buf));
puts(buf);
或者:先分配空间然后设置
char* str = (char*)malloc(10 * sizeof(char)); // 超过会被截断
gets_s(str, 10);
puts(str);
getc\\putc
//stdin可以替换为stdout或者文件流FileStream,超过会被截断
char a = getc(stdin);
putc(a, stdout);
getchar\\putchar
char a = getchar();
putchar(a);
char buf[20];
int n = fgets(buf, sizeof(buf)/sizeof(buf[0]), stdin)
fopen\\fopen_s
char buf[20];
FILE* file;
fopen_s(&file, "./test.txt", "a+");
fprintf_s(file, "%s", buf);
字符集
详见:地址
C 语言诞生时,只考虑了英语字符,使用7位的 ASCII 码表示所有字符,ASCII 码的范围是0到127。
Unicode 为每个字符提供一个号码,称为码点(code point),其中0到127的部分,跟 ASCII 码是重合的。通常使用“U+十六进制码点”表示一个字符,比如U+0041
表示字母A
。
为了适应不同的使用需求,Unicode 标准委员会提供了三种不同的表示方法,表示 Unicode 码点。
- UTF-8:使用1个到4个字节,表示一个码点。不同的字符占用的字节数不一样。
- UTF-16:对于U+0000 到 U+FFFF 的字符(称为基本平面),使用2个字节表示一个码点。其他字符使用4个字节。
- UTF-32:统一使用4个字节,表示一个码点。