C语言基础知识

本文详细介绍了C语言中的extern关键字的使用,包括在单个文件和头文件中的声明。还涵盖了变量声明、初始化,特别是数组、函数指针的概念和操作,以及枚举、C风格字符串的处理。此外,讨论了内存管理和命令行参数,以及常用输入输出函数和头文件的使用。
摘要由CSDN通过智能技术生成

extern 关键字使用

可声明变量和函数。

  1. 在单个文件内,类似普通声明

  2. 在别的.c文件内定义,在本.c文件内直接使用,别的文件要包含<stdio.h>

    origin.c

    #include <stdio.h>
    int num = 0;
    

    user.c

    #include <stdio.h>
    extern int num;
    
  3. 在别的头文件.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\

变量声明:

  1. 在函数或块内部的局部变量

  2. 在所有函数外部的全局变量

  3. 形式参数的函数参数定义中:如

    #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;
}
  1. 直接使用函数名
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

  1. 使用函数指针
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
  1. size+i = &size[i]
  2. size = &size

坑:

  1. 不能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风格字符串的函数:

  1. strcpy
  2. strcat
  3. strlen
  4. strcmp
  5. strchr
  6. 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. 点击->调试->窗口->内存
  2. 然后右键要监视的结构体变量
  3. 在地址栏输入结构体变量地址或者&结构体名取值
    请添加图片描述

右键更改显示方式和列等:

请添加图片描述

示例1:

请添加图片描述

可以发现book2第一个字节存储的内容为253,二进制为1111,1101。存储结构如下图:

请添加图片描述

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_FILE

the 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个字节,表示一个码点。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值