当前位置: 首页 > news >正文

C代码学习笔记(一)

1、头文件

#include <stdio.h>     // 标准输入输出库(printf, scanf等)
#include <stdlib.h>    // 标准库(malloc, free等)
#include <string.h>    // 字符串处理库(strlen, strcpy等)
#include <math.h>      // 数学函数
#include <time.h>      // 时间函数
#include <ctype.h>     // 字符处理

2、char*

1. 核心概念:char* 是什么?

char* 是一个指针,它指向一个或多个字符(char)所在的内存地址。

你可以从两个主要角度来理解它:

  1. 指向单个字符的指针

    char c = 'A';
    char* ptr = &c; // ptr 指向变量 c 的内存地址printf("%c\n", *ptr); // 输出: A (通过指针解引用获取它指向的值)

  2. 指向字符串的指针(更常见)
    在 C 语言中,字符串是以空字符 '\0' 结尾的字符数组。char* 常常被用来指向这样一个数组的起始地址

    char* str = "Hello"; // str 指向字符串字面量 "Hello" 的第一个字符 'H' 的地址
    printf("%s\n", str); // 输出: Hello

    printf 的 %s 格式说明符会从 str 指向的地址开始,逐个打印字符,直到遇到 '\0' 为止。


2. 类似语法和对比

理解 char* 的关键之一是把它和类似的语法结构进行对比。

a) char* str vs char str[]

这是最容易混淆的一对。它们看起来很相似,但在内存分配和可修改性上有本质区别。

特性char* str = "Hello";char str[] = "Hello";
内存位置str 是一个指针变量,存储在栈上。它指向的文字 "Hello" 存储在只读数据段(或常量区)。整个数组 str 在栈上被分配内存,并初始化为 {'H','e','l','l','o','\0'}
可修改性字符串内容不可修改(行为未定义,通常导致程序崩溃)。
str[0] = 'h'; // ERROR!
字符串内容可以修改
str[0] = 'h'; // OK!
str 本身str 是一个变量,可以指向别的字符串。
str = "World"; // OK!
str 是数组名,是一个常量指针,不能指向别处。
str = "World"; // ERROR!
sizeofsizeof(str) 返回指针的大小(例如 4 或 8 字节)。sizeof(str) 返回整个数组的大小(6 字节,包含 \0)。

简单比喻:

  • char* str:像一张写着朋友家地址的纸条。你可以擦掉纸条上的地址,换成另一个朋友的地址(str = "World")。但你不能根据这个地址去修改朋友家的房子(str[0] = 'h')。

  • char str[]:像你自己家的一块地皮和建好的房子。你不能把整块地皮搬到别处去(str = "World"),但你可以随意装修自己家的房子(str[0] = 'h')。

b) const char*

由于用 char* 指向字符串字面量有修改的风险(这是危险的),最佳实践是总是使用 const char*

const char* str = "Hello";
// str[0] = 'h'; // 编译器会直接报错,避免了运行时崩溃

这明确告诉编译器和你自己:str 指向的数据是常量,不可修改。


3. 常见用法示例

a) 遍历字符串
#include <stdio.h>int main() {const char* s = "Hello";// 方法 1: 数组下标法for (int i = 0; s[i] != '\0'; i++) {printf("%c ", s[i]);}printf("\n");// 方法 2: 指针算术法 (更C风格)const char* p = s; // p 指向字符串开头while (*p != '\0') { // 只要 p 指向的字符不是结束符printf("%c ", *p);p++; // 让 p 指向下一个字符的地址}return 0;
}
b) 作为函数参数

函数需要处理字符串时,通常传入 const char*(如果不想修改字符串)或 char*(如果需要修改字符串)。

// 计算字符串长度
int my_strlen(const char* str) {int len = 0;while (*str != '\0') {len++;str++; // 指针移动到下一个字符}return len;
}// 修改字符串内容
void to_uppercase(char* str) {while (*str != '\0') {if (*str >= 'a' && *str <= 'z') {*str = *str - ('a' - 'A'); // 转换为大写}str++;}
}int main() {const char* s1 = "hello";printf("Length: %d\n", my_strlen(s1)); // 输出 5char s2[] = "hello"; // 必须是数组才能修改to_uppercase(s2);printf("%s\n", s2); // 输出 "HELLO"return 0;
}

4. 动态内存分配

char* 也常用于动态分配内存来创建字符串,这在需要可变长度字符串时非常有用。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>int main() {// 分配足够的内存来存储 "Hello" (5字符 + 1空字符 = 6字节)char* str = (char*)malloc(6 * sizeof(char));// 检查分配是否成功if (str == NULL) {printf("Memory allocation failed!\n");return 1;}// 复制字符串到新分配的内存中strcpy(str, "Hello");printf("%s\n", str); // 输出: Hello// 可以安全地修改,因为它在堆上str[0] = 'h';printf("%s\n", str); // 输出: hello// 非常重要:使用完后释放内存free(str);str = NULL; // 避免野指针return 0;
}

总结

概念描述关键点
char*指向字符或字符串的指针。理解指针本身和它指向的数据。
char* str指向(通常是只读的)字符串字面量。字符串不可修改,指针可指向别处。
char str[]一个字符数组。字符串可修改,数组名不可指向别处。
const char*指向常量字符串的指针。安全,明确意图,推荐使用。
动态分配使用 malloc 为 char* 在堆上分配内存。字符串可修改,必须手动 free

3、字符串长度获取

strlen():获取字符串长度(不包括结尾的'\0'),返回字符串中字符的个数

示例:

char text[] = "Hello";
int len = strlen(text);  // len = 5// sizeof 与 strlen 的区别
char arr[10] = "Hi";
int size1 = sizeof(arr);   // 10 (数组总大小)
int size2 = strlen(arr);   // 2  (实际字符串长度)

4、malloc 

malloc(Memory ALLOCation)是 C 标准库 <stdlib.h> 中的一个函数,用于在堆(Heap) 上动态地分配指定大小的内存块。

成功时返回指向分配内存起始地址的 void* 指针,失败时返回 NULL

// 分配整型数组
int* numbers = malloc(10 * sizeof(int));// 分配结构体
typedef struct {int id;char name[20];
} Person;
Person* person = malloc(sizeof(Person));// 记得检查分配是否成功
int* ptr = malloc(100 * sizeof(int));
if (ptr == NULL) {printf("内存分配失败!\n");exit(1);
}

为字符串分配内存

#include <stdlib.h>
#include <stdio.h>int main() {// 分配存储 "Hello" 的内存:5个字符 + 1个空字符 '\0'char* str = (char*)malloc(6 * sizeof(char));if (str == NULL) {printf("内存分配失败!\n");return 1;}// 使用内存...free(str);str = NULL;return 0;
}

重要注意事项

  1. 必须检查返回值malloc 可能失败(内存不足时)

  2. 必须释放内存:用 free() 释放,否则内存泄漏

  3. 类型转换malloc 返回 void*,需要转换为具体类型

  4. 计算大小:使用 sizeof 确保跨平台兼容性

内存操作函数

a) memcpy - 内存复制

void* memcpy(void* dest, const void* src, size_t num);
// 按字节复制,不关心 '\0'

b) memmove - 安全内存移动

void* memmove(void* dest, const void* src, size_t num);
// 处理内存重叠的情况

c) memset - 内存设置

void* memset(void* ptr, int value, size_t num);
// 将内存块设置为特定值

示例

int cnt[26];           // 声明一个包含26个整数的数组
memset(cnt, 0, sizeof(cnt));  // 将数组的所有元素设置为0
  1. int cnt[26];

    • 声明一个名为 cnt 的数组

    • 数组包含26个 int 类型的元素

    • 通常用于存储26个英文字母的计数(a-z)

  2. memset(cnt, 0, sizeof(cnt));

    • memset():内存设置函数,用于将一块内存区域填充为指定的值

    • 第一个参数 cnt:要设置的内存起始地址(这里就是数组首地址)

    • 第二个参数 0:要填充的值(这里用0填充)

    • 第三个参数 sizeof(cnt):要设置的字节数

      • sizeof(cnt) 返回整个数组占用的字节数

      • 如果 int 是4字节,那么 sizeof(cnt) = 26 × 4 = 104 字节

5、strcpy 

strcpy(STRING COPY)是 C 标准库 <string.h> 中的函数,用于将一个字符串(包括终止的空字符 '\0')复制到另一个字符串。

函数原型

char* strcpy(char* destination, const char* source);
  • 参数1destination - 目标字符串的指针(必须有足够空间)

  • 参数2source - 源字符串的指针(不会被修改)

  • 返回值:返回目标字符串的指针

#include <stdio.h>
#include <string.h>int main() {char source[] = "Hello World";char destination[20]; // 确保目标有足够空间strcpy(destination, source);printf("源字符串: %s\n", source);      // Hello Worldprintf("目标字符串: %s\n", destination); // Hello Worldreturn 0;
}

strcpy 最大的问题是不检查目标缓冲区的大小,如果源字符串比目标缓冲区长,会导致缓冲区溢出,这是严重的安全漏洞。

安全替代方案:strncpy

char* strncpy(char* destination, const char* source, size_t num);
  • 参数1destination - 目标缓冲区

  • 参数2source - 源字符串

  • 参数3num - 最多复制的字符数

  • 返回值:目标字符串的指针

特点和行为

  1. 复制最多 num 个字符从源到目标

  2. 如果源字符串长度小于 num:用 '\0' 填充剩余空间

  3. 如果源字符串长度大于等于 num不会自动添加 '\0'

  4. 不会检查目标缓冲区大小(但比 strcpy 安全)

// 安全使用 strncpy 的模板
char destination[SIZE];
strncpy(destination, source, sizeof(destination) - 1);
destination[sizeof(destination) - 1] = '\0'; // 确保终止

其他重要字符串函数

a) strcpy - 基本字符串复制         

char* strcpy(char* dest, const char* src);
// 危险:不检查缓冲区大小

b) strcat - 字符串拼接

char* strcat(char* dest, const char* src);
// 将 src 追加到 dest 末尾

c) strncat - 安全字符串拼接

char* strncat(char* dest, const char* src, size_t num);

示例:

char dest[20] = "Hello";
strncat(dest, " World!", sizeof(dest) - strlen(dest) - 1);
// 自动添加 '\0',比 strncpy 更友好

d) strcmp - 字符串比较

int strcmp(const char* str1, const char* str2);
// 返回 0:相等, >0:str1>str2, <0:str1<str2

e) strncmp - 安全字符串比较

int strncmp(const char* str1, const char* str2, size_t num);
// 比较前 num 个字符

f) strlen - 字符串长度

size_t strlen(const char* str);
// 返回字符串长度(不包括 '\0')

g) strchr - 查找字符

char* strchr(const char* str, int character);
// 返回字符第一次出现的位置,找不到返回 NULL

h) strstr - 查找子串

char* strstr(const char* haystack, const char* needle);
// 返回子串第一次出现的位置
#include <stdio.h>
#include <string.h>int main() {char haystack[] = "hello world";char needle[] = "world";char* result = strstr(haystack, needle);printf("haystack地址: %p\n", haystack);printf("result地址:   %p\n", result);printf("偏移量:       %ld\n", result - haystack);printf("下标:         %d\n", (int)(result - haystack));printf("找到的字符串: %s\n", result);return 0;
}haystack地址: 0x7ffd5a3b2a10
result地址:   0x7ffd5a3b2a16
偏移量:       6
下标:         6
找到的字符串: world

i) strtok - 字符串分割

char* strtok(char* str, const char* delimiters);
// 用于分割字符串

#include<stdio.h>
#include<string.h>
#include<stdlib.h>char* findTheDifference(char* s, char* t) {int n = strlen(s), m = strlen(t);char* str = malloc(n + m + 1);strcpy(str,s);strcat(str,t);return str;}int main () {char* result = findTheDifference("Hello", "World");printf("结果:%s\n", result);free(result);return 0;
}

7、位运算符

假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:

运算符描述实例
&

对两个操作数的每一位执行逻辑与操作,如果两个相应的位都为 1,则结果为 1,否则为 0。

按位与操作,按二进制位进行"与"运算。运算规则:

0&0=0;   
0&1=0;    
1&0=0;     
1&1=1;
(A & B) 将得到 12,即为 0000 1100
|

对两个操作数的每一位执行逻辑或操作,如果两个相应的位都为 0,则结果为 0,否则为 1。

按位或运算符,按二进制位进行"或"运算。运算规则:

0|0=0;   
0|1=1;   
1|0=1;    
1|1=1;
(A | B) 将得到 61,即为 0011 1101
^

对两个操作数的每一位执行逻辑异或操作,如果两个相应的位值相同,则结果为 0,否则为 1。

异或运算符,按二进制位进行"异或"运算。运算规则:

0^0=0;   
0^1=1;   
1^0=1;  
1^1=0;
(A ^ B) 将得到 49,即为 0011 0001
~

对操作数的每一位执行逻辑取反操作,即将每一位的 0 变为 1,1 变为 0。

取反运算符,按二进制位进行"取反"运算。运算规则:

~1=-2;   
~0=-1;
(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
<<

将操作数的所有位向左移动指定的位数。左移 n 位相当于乘以 2 的 n 次方。

二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

A << 2 将得到 240,即为 1111 0000
>>

将操作数的所有位向右移动指定的位数。右移n位相当于除以 2 的 n 次方。

二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补 0,负数左补 1,右边丢弃。

A >> 2 将得到 15,即为 0000 1111
A ^= B;
// 等价于
A = A ^ B;

http://www.dtcms.com/a/347385.html

相关文章:

  • Windows打开命令窗口的几种方式
  • 使用 PSRP 通过 SSH 建立 WinRM 隧道
  • 注意力机制中为什么q与k^T相乘是注意力分数
  • 每日定投40刀BTC(22)20250802 - 20250823
  • 编程刷题-染色题DFS
  • 03_数据结构
  • 在 CentOS 7 上搭建 OpenTenBase 集群:从源码到生产环境的全流程指南
  • MSPM0G3507工程模板创建
  • 微信小程序自定义组件开发(上):从创建到数据通信详解(五)
  • 纠删码技术,更省钱的分布式系统的可靠性技术
  • 使用springboot开发-AI智能体平台管理系统,统一管理各个平台的智能体并让智能体和AI语音设备通信,做一个属于自己的小艾同学~
  • Dubbo vs Feign
  • 个人思考与发展
  • 探秘北斗卫星导航系统(BDS):架构、应用与未来蓝图,展现中国力量
  • 详细说一说JIT
  • Redis面试精讲 Day 28:Redis云原生部署与Kubernetes集成
  • Js逆向 拼夕夕anti_content
  • 深入解析Spring Boot自动配置原理:简化开发的魔法引擎
  • Java基础第2天总结
  • 青少年机器人技术(四级)等级考试试卷-实操题(2021年12月)
  • 互联网大厂Java面试实战:核心技术栈与场景化提问解析(含Spring Boot、微服务、测试框架等)
  • Java 遗传算法在中药药对挖掘中的深度应用与优化策略
  • 雨雾天气漏检率骤降80%!陌讯多模态车牌识别方案实战解析
  • Redis--day10--黑马点评--秒杀优化消息队列
  • 【JavaEE】多线程 -- JUC常见类和线程安全的集合类
  • 什么猫粮好?2025最新猫粮排名合集
  • 深度解析Bitmap、RoaringBitmap 的原理和区别
  • MySql知识梳理之DDL语句
  • TypeScript 类型系统入门:从概念到实战
  • 从零开始学习JavaWeb-16