The second week

一、指针:

指针是变量,可以赋值,可以作为参数和返回值

const int* p;----p指向的内容无法被修改
int* const p;----p变量的值无法被修改
const int* const p;----p变量的值和p变量指向的内容都无法被修改


①一级指针变量

int i=10;
int* p = &i;//声明一个一级指针变量,并初始化
//此时使用变量p可以直接操作变量i

请添加图片描述


②二级指针变量

char* arr[5];//定义了一个指针数组(数组内的元素都是指针)
//我们知道数组名可以代表整个数组
//但是在给函数传参或给其他变量赋值时,数组名就退化为了指向数组第一个元素的的地址
int* p = arr;//此时的arr实际上是&arr[0]
//arr[0]是一个指针,&arr[0]表示对一个指针取地址,得到了指针的地址,所以需要二级指针变量去接收

int**p = arr//这是正确的版本

③指针传参

void swap_1(int a, int b)
{
	int c = b;
	b = a;
	a = c;
}
void swap_2(int* a, int* b)
{
	int c = *b;
	*b = *a;
	*a = *c;
}
int main(int argc, char* argv[])
{
	int a = 10, b = 15;
	swap_1(a, b);
	//此时a = 10, b = 15,没有变化
	//因为C语言的函数是值传递,swap_1函数中只保留了a,b的副本,并不会改变主调函数中的变量
	swap_1(&a, &b);
	//此时a = 15, b = 10
	//被调函数会根据a,b的地址直接修改主调函数中的变量
	return 0
}

为了改变a,b的值,所以传递力他们的地址


void function(char** p)//传入的是指针的地址,需要用二级指针接收
{
	char* q = NULL;
	q = p[0];
	p[0] = p[2];
	p[2] = q;
}
int main(int argc, char* argv[])
{
	char* arr[3] = {"huzhu", "wangzi", "xiaozhuanfeng"};
	//如何使用一个函数改变arr中元素的顺序呢?需要传什么参数呢?函数该用几级指针来接收
	function(arr);//相当于传入了&arr[0]
	//此时arr中的元素顺序发生了改变
	return 0;
}

为了改变arr中的值,传递了arr的地址

由此可以知道要改变谁的值就传递谁的地址
如果要改变的对象是普通变量就用一级指针接收
如果要改变的对象是指针变量就用二级指针接收


④指针作为返回值

!!!不可以返回当前栈帧中变量的地址,会使返回的指针变为野指针


⑤函数指针

指向函数的指针

int fun(int a, int b)
{
	return a + b;
}

int main(void)
{
	fun 和 &fun 都表示fun这个函数的地址
	int(*p)(int, int) = fun;//表示p是指向fun这个函数的指针
	int(*p)(int, int) = &fun;
	
	//使用
	*p(a, b);
	p(a, b);
	以上两种调用方式是等价的
	return 0;
}

一个使用函数指针的例子

void qsort( void *ptr, size_t count, size_t size,int (*comp)(const void *, const void *) );

qsort可以对任意类型的数组进行排序
int (*comp)(const void *, const void *)是函数指针,表示比较规则

⑥指针的算术运算

指针 - 指针 = 整数;
指针 + 整数 = 指针;
指针 - 整数 = 指针;

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
p[3] == arr[3]//p[3]等价于*(p+3)

二、字符串:

C语言中没有字符串类型,字符串是由字符数组实现的
字符串之间不可以直接赋值,需要使用strcpy函数进行复制

字符串和字符数组的区别

以’\0’结尾则是字符串,以其他字符结尾的是字符数组


①字符串字面值常量

"huzhu"//一个字符串字面值,存放在代码段,不可被修改的值
char* p = "huzhu";//p指向了一个字符串字面值常量
p[2] == 'u'//可以访问其中的元素,但是不可以修改
"huzhu"[2] =='u'//这种方式也可以访问其中的元素,但也不能修改

//书写方式
printf("baixiuzhu");
printf("bai"
	   "xiu"
	   "zhu");
// 如果两个字符串字面值之间仅有空白字符,那么这两个字符串字面“相邻”
// C语言编译器在编译时,会将相邻的字面值拼接在一起。
printf("bai\
xiu\
zhu")
//'\'将后面的换行符转义了,所以在编译器看来仍然是一个字符串
//(第一行之后的一定要顶格写,否则字符串中间会插入空白字符)
//以上三种书写方式都表示同样的字符串

②字符串变量

//定义与赋值
char arr[] = {'h', 'e', 'l', 'l', 'o', '\0'};
char arr[] = "hello";
//二者等价
!!!第二个不等于拿一个字符串给另一个字符串赋值!!!

char arr[5] = "hello";//是字符数组,不表示字符串,结尾不是'\0'
char arr[6] = "hello";//是字符串,结尾是'\0'

③字符串的输入和输出

printf()和putc()

char arr[] = "baixiuzhu";
printf("%s %s", "huzhu", arr);
putc(arr);

//二者区别
printf:当匹配到'\0'时结束匹配,如果没有匹配到'\0'则一直往后匹配,在输出字符串之后不会有其他操作
	   如果只想显示字符串的一部分,可以使用转换说明 %.ps ,这里 p 表示是要显示的字符数量
	   转换说明%ms会在大小为m的字段内显示字符串。(对于超过m个字符的字符串,会显示整个字符串,不会
	   截断)。如果字符串少于m个字符,则会在字段内右对齐。如果要左对齐,可以在m前加一个负号。
	   
putc:当匹配到'\0'时结束匹配,如果没有匹配到'\0'则一直往后匹配,在输出字符串之后会打印一个换行符

scanf()和getc()

char* p;
char arr[20];
scanf("%s", p, arr);//不需要添加&,因为p,arr本身就是地址
getc(p);

//二者区别
scanf:忽略前面的空白字符,遇到空白字符时停止,并在后面补一个'\0',适合读取一个单词
getc:直接开始读取不会忽略开始的空白字符,遇到'\n'时停止,并将其换成'\0',适合读取一行字符

③库函数

size_t strlen(char* str)

size_t strlen(char* str)
{
	char* p = str;
	while(*p)
		++p;
	return p - str;
}

char* strcpy(char* s1, const char* s2)

不会检查s1是否越界

char* strcpy(char* s1, const char* s2)
{
	char* p = s1;
	while(*p++ = *s1++);
	return s1;
}

char* strncpy(char* s1, const char* s2, size_t count)

s1可能不是以'\0结束的'

1、复制 s2 所指向的字符数组的至多 count 个字符(包含空终止字符,但不包之后的任何字符)到 s1 所指向的字符数组。
2、若在完全复制整个 s2 数组前抵达 count ,则返回的的字符数组不是以’\0’结尾的。
3、若在复制来自 s2 的空终止字符后未抵达 count ,则写入额外的空字符到 s1,直至写入 count 个字符。
4、若字符数组重叠,若 dest 或 src 不是指向字符数组的指针(包含若 dest 或 src 为空指针),若 dest 所指向的数组大小小于 count ,或若 src 所指向的数组大小小于 count 且它不含空字符,则行为未定义。

int strcmp(const char* s1, const char* s2)

int my_strcmp(const char* s1, const char* s2) 
{
	while (*s1 && *s2) {
		if (*s1 != *s2) {
			return *s1 - *s2;
		}
		s1++;
		s2++;
	} 
	return *s1 - *s2;
}

char* strcat(char* s1, const char* s2)

字符串拼接 不会检查s1是否越界

char* strcat(char* s1, const char* s2) 
{
	char* start = s1;
	while (*s1)
		s1++;

	while (*s1++ = *s2++);
	return start;
}

char* strcat(char* s1, const char* s2, size_t count)

1、后附来自 s2 所指向的字符数组的至多 count 个字符,到 s1所指向的字符串的末尾,若找到空字符则停止。字符 s2[0] 替换位于 s1 末尾的’\0’。始终后附终止空字符到末尾(故函数可写入的最大字符数是 count+1 )。
2、若目标数组没有对于 s1和 s2 的首 count 个字符加上终止空字符的足够空间,则行为未定义。
3、若源与目标对象重叠,则行为未定义。
3、若 s1不是指向空终止字节字符串的指针,或 s2 不是指向字符数组的指针,则行为未定义。


命令行参数

命令函参数是操作系统传递给应用程序的main函数的参数,可以改变程序的执行方式

命令行参数都是字符串,argc表示参数的个数,argv是用来接收参数
第一个参数一定是应用程序的路径

int main(int argc, char* argv[]){};

三、动态内存分配

void * malloc(size_t count);

在堆上申请大小为count的连续空间,并将空间的首字节地址返回

void * calloc(size_t count, size_t size);

在堆上申请大小为size,数量为count的连续空间,并将空间的首字节地址返回
calloc函数申请的空间会被初始化为0,其他两个都不会初始化

void * calloc(void ptr, size_t size);

对ptr指向的空间扩容或缩容
ptr一定是通过这三个函数返回的地址,否则无法扩容
扩容:如果原地无法申请到size大小的空间则在其他地方申请,并将地址返回,如果原地可以申请
到,则返回原来空间的指针,扩容后的那一部分空间不会初始化
缩容:size小于prt原本的大小

void free(void ptr)

ptr一定是通过三个函数返回的地址,否则无法释放空间


四、结构体和枚举

结构体

结构体在内存中有内存对齐现象,为的是减少读取次数

// 定义类型
struct student //一般定义
{
	int number;	
	char name[25];
};
typedef struct node //定义别名
{
	int number;	
	struct node* next;
} Node;
struct//匿名结构体
{
	int i;
};

int main(void)
{
	struct student s;
	Node* n;
}
//定义别名
typedef struct node
{
	int number;	
	node* next;
} Node;
//在C语言中这种形式无法通过编译

结构体之间可以直接赋值,
s1=s2;
对于指向结构体变量的指针p
*( p).成员 等价于 p->成员


枚举

枚举值是整数,用于表示一些离散值

// 定义枚举类型
enum Suit
{
	DIAMOND = 1, 
	CLUB = 2, 
	HEART = 4, 
	SPADE = 8
};
//给枚举类型起别名
typedef enum 
{
	DIAMOND = 1, 
	CLUB = 2, 
	HEART = 4, 
	SPADE = 8
} Suit;


如果不赋值,则枚举值默认从0开始 0,1,2,3...

五、文件

在 C 语言中,流 (stream) 表示 任意输入的源 任意输出的目的地

1、模型

在这里插入图片描述

  1. 这里的输入和输出部分就是流,如果不指定流的类型,那么
    ① 输入流默认为标准输入流(stdin)
    ②输出流默认为标准输出流(stdout)。
  2. 缓冲区是以先进先出的方式管理数据的。缓冲区分为三种类型:
    ①满缓冲。当缓冲区空时,从输入流中读取数据;当缓冲区满时,向输出流中写入数据。
    ②行缓冲。每次从输入流中读取一行数据;每次向输出流中写入一行数据 (stdin, stdout)。
    ③无缓冲。只要缓冲区存在数据就将数据输出 (stderr)。

2、标准流

C 语言对流的访问是通过文件指针实现的,它的类型为FILE* 。并且在 <stdio.h> 头文件中提供了 3 个
标准流。这 3 个标准流可以直接使用——我们不需要对其进行声明,也不用打开或者关闭它们。

文件指针默认含义文件描述符
stdin标准输入键盘0
stdout标准输出屏幕1
strerr标准错误屏幕2

3、文本文件和二进制文件

C 语言支持两种类型的文件:文本文件和二进制文件。文本文件中存储的是字符数据,人类是可以看懂的;二进制文件中的数据,人类是看不懂的。

文本文件具有两个独特的性质:

文本文件有行的概念。文本文件被划分为若干行,并且每一行的结尾都以特殊字符进行标记。

  1. 在 Windows 系统中,是以回车符和换行符 (\r\n) 进行标记的
  2. 在 Unix 和 Macintosh 系统中是以换行符 (\n) 标记的。
  3. 早期的 Macintosh 是以回车符 (\r) 标记每一行的结尾的。

文本文件可能包含一个特殊的 “文件末尾” 标记。一些操作系统允许在文本文件的末尾使用一个特殊的字节作为标记。

  1. 在 Windows 系统中,这个标记为 ‘\x1a’ (Ctrl+Z)。Ctrl+Z不是必需的,但如果存在,它就标志着文件的结束,其后的所有字节都会被忽略。
  2. 大多数其他操作系统 (包括 UNIX) 是没有文件末尾字符。使用 Ctrl+Z 的这一习惯继承自 DOS,而DOS 中的这一习惯又是从 CP/M (早期用于个人电脑的一种操作系统) 来的

4、文件相关的函数

1、打开 / 关闭文件

FILE* fopen(const char* filename, const char* mode)

第一个参数是文件的路径,用来定位文件的;第二个参数表示是以何种模式打开文件的。如果打开成功,返回指向文件第一个字符的文件流指针;如果无法打开文件,返回NULL

  1. 文件路径可以是绝对路径(打开系统文件),也可以是相对路径(打开项目文件)。
  2. 打开模式有文本文件方式和二进制方式(b)。

mode

模式字符串含义文件存在文件不存在
“r”打开文件用于读不会清空报错
“w”打开文件用于写清空文件创建文件
“a”打开文件用于追加不会清空创建文件
“r+”打开文件用于读和写不会清空报错
“w+”打开文件用于读和写清空文件文创建文件
“a+”打开文件用于读和写不会清空创建文件
模式字符串含义文件存在文件不存在
“rb”打开文件用于读不会清空报错
“wb”打开文件用于写清空文件创建文件
“ab”/打开文件用于追加不会清空创建文件
“rb+”/“r+b”打开文件用于读和写不会清空报错
“wb+”/“w+b”打开文件用于读和写清空文件文创建文件
“ab+”/“a+b”打开文件用于读和写不会清空创建文件
int fclose(FILE* stream)

如果关闭成功,返回 0 ;失败返回 EOF (-1)
当不再使用某个文件时,一定要及时关闭该文件。


2、读 / 写文件

int fgetc(FILE* stream)

从输入流中读取一个字符,如果读取成功,返回读取的字符;如果读到文件末尾,或者读取失败,返回 EOF
fgetc 和 getchar 类似。不同的是 getchar 只能从标准输入流(stdin)中读取字符,而 fputc 可以从任意流中读取字符。

int fputc(int c, FILE* stream)

向输出流中写入一个字符,如果写入成功,返回写入的字符;如果写入失败,返回EOF。
fputc 和 putchar 类似。不同的是 putchar 只能向标准输出流(stdout)中写入字符,而fputc 可以向任意流中写入字符。


char* fgets(char* str, int count, FILE* stream)

从输入流 stream 中,最多读取 count - 1 个字符,并把读取的字符存入 str 指向的字符数组中。
fgets 遇到换行符’\n’,或者文件的末尾就会终止(也就是说,读取的字符数可能不足 count - 1 个),并且会存储换行符’\n’。fgets 会在最后添加空字符’\0’。

  1. 参数:
    str: 指向一个字符数组
    count: 能够写入的最大字符数量(通常是str指向字符数组的长度)
    stream: 输入流
  2. 返回值:
    成功:返回str
    失败:NULL

fgets 是 gets 的通用版本,它可以从任意输入流中读取数据,而 gets 只能从 stdin 中读取数据。fgets 也比 gets 更为安全,因为它限制了读取字符的最大数目(count - 1)。此外,如果读取了换行符而终止,那么它会存储换行符’\n’,而 gets 函数从来不会存储换行符。

int fputs(const char* str, FILE* stream)

将 str 指向的字符串,写入输出流 stream 中。

  1. 参数:
    str: 要写的字符串(以’\0’结尾的字符串)
    stream: 输出流
  2. 返回值:
    成功:返回一个非负值。
    失败:返回EOF,并设置errno

fputs 是 puts 的通用版本,它可以将字符串写入到任意的输出流中,而 puts 只能写入到 stdout 中。此外,fputs 是原样输出字符串,而 puts 会在字符串后面而外输出一个换行符’\n’。


int fscanf(FILE* stream, const char* format, …)

fscanf 和 scanf 类似,是用来进行格式化输入的。
不同的是,scanf 是从标准输入(stdin)中读取数据,而 fscanf 可以从任何一个流中读取数据。也就是说,当 fscanf 的第一个参数为 stdin 时,它的效果等价于 scanf 。
此外,sscanf 可以从字符串中读取数据。

int fprintf(FILE* stream, const char* format, …)

fprintf 和 printf 类似,是用来进行格式化输出的。
不同的是,printf 始终是向标准输出(stdout)写入内容的,而 fprintf 可以向任何一个输出流中写入内容。也就是说,当 fprintf 的第一个参数为 stdout 时,它的效果等价于 printf 。
此外,sprintf 可以将内容写入到一个字符数组中。

格式化输入输出,可以用于序列化和反序列化过程中。所谓序列化,就是将程序中的对象转换成一种可以保存的格式(二进制或文本),从而方便存储(存储到文件或数据库中)或传输(通过网络传输给另一台机器)。反序列化则是序列化的逆过程,它将按一定格式存储的数据转换成程序中的对象。


size_t fread(void* buffer, size_t size, size_t count, FILE* stream);

fread 从输入流 stream 中,最多读取 count 个元素,并把它们依次存放到 buffer 指向的数组中。

  1. 参数:
    buffer: 指向存放数据的数组
    size: 每个元素的大小(以字节为单位)
    count: 最多可以读取的元素个数
    stream: 输入流
  2. 返回值:
    成功读取元素的个数。当读到文件末尾,或者发生错误时,返回值可能小于count。我们可以通过feofferror函数来判断,到底是读到了文件末尾,还是发生了错误。
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)

fwrite 将存放在 buffer 指向的数组中的 count 个元素写入到输出流 stream 中。

  1. 参数:
    buffer: 指向存放数据的数组。
    size: 每个元素的大小(以字节为单位)
    count: 要写入元素的个数
    stream: 输出流
  2. 返回值:
    成功写入元素的个数。当发生错误时,这个值可能小于count。

fread/fwrite 不仅可以用于读写二进制文件,还可以用于序列化和反序列化过程中。


3、文件定位

每个流都有相关联的文件位置。在执行读写操作时,文件位置会自动推进,并按照顺序访问文件。顺序访问是很好的,但是有时候,我们可能需要跳跃地访问文件。为此,<stdio.h> 提供了几个函数来支持这种能力:

int fseek(FILE* stream, long=-ooffset, int whence)

fseek 可以改变与 stream 相关联的文件位置。其中 whence 表示参照点,参照点有 3 个选择:

  1. SEEK_SET:文件的起始位置。
  2. SEEK_CUR:文件的当前位置。
  3. SEEK_END:文件的末尾位置。

offset 表示偏移量 (可能为负),它是以字节进行计数的。比如:

  1. 移动到文件的起始位置,可以这样写:fseek(fp, 0L, SEEK_SET);
  2. 移动到文件的末尾,可以这样写:fseek(fp, 0L, SEEK_END);
  3. 往回移动 10 个字节,可以这样写:fseek(fp, -10L, SEEK_CUR);

通常情况下,fseek 会返回 0;如果发生错误 (比如,请求的位置不存在),那么fseek 会返回非 0 值。

long ftell(FILE* stream)

ftell 以长整数形式返回当前文件位置;如果发生错误,ftell 返回 -1L。
ftell 一般的用法是:记录当前位置,方便以后返回。

void rewind(FILE* stream)

rewind 会将文件位置设置为起始位置,类似于调用:fseek(fp, 0L, SEEK_SET);

5、错误处理

errno

errno 是一个 int 类型的全局变量 (C11 修改为线程本地变量,即每个线程都有一个独有的 errno 变量),它定义在 <errno.h> 头文件中。标准库中有些函数 (比如与文件相关的一些函数),如果在调用过程中发生了错误,它会设置 errno 的值,以表明发生了何种类型的错误。
程序启动时,会将 errno 的值设为 0,表示没有错误发生。其它非 0 值都表示发生了某种类型的错误。
我们可以通过
perror 和strerror 来显示错误信息。其中,strerror 定义在 <string.h> 头文件中。

Week 1 During the first week of my Java backend internship, I was introduced to the company's development environment and tools. I also familiarized myself with the project requirements and the existing codebase. My mentor assigned me to work on a simple CRUD operation for a user entity. Week 2 In the second week, I continued working on the user entity CRUD operation. I implemented the backend logic to create, read, update, and delete users. I also worked on handling errors and exceptions that may occur during the operation. Week 3 During the third week, I worked on integrating the user entity CRUD operation with the frontend. I implemented the REST API endpoints and tested them using Postman. I also worked on improving the code quality by following coding standards and guidelines. Week 4 In the fourth week, I worked on optimizing the user entity CRUD operation by implementing caching and pagination. I also learned about database indexing and implemented it to improve the performance of the application. Week 5 During the fifth week, I worked on implementing authentication and authorization for the application. I used Spring Security to secure the REST API endpoints and implemented token-based authentication. Week 6 In the sixth week, I worked on implementing a feature to upload and store user profile images. I used AWS S3 to store the images and implemented the logic to upload and retrieve images. Week 7 During the seventh week, I worked on implementing a feature to send emails to users. I used JavaMail API to send emails and implemented the logic to send verification emails to newly registered users. Week 8 In the eighth week, I worked on improving the performance of the application by implementing caching at different layers of the application. I also worked on optimizing the database queries to reduce the response time. Week 9 During the ninth week, I worked on implementing a feature to generate PDF reports for the application. I used iText library to generate the PDF files and implemented the logic to generate reports based on user data. Week 10 In the tenth week, I worked on implementing a feature to schedule tasks to run at specific times. I used Quartz scheduler to schedule tasks and implemented the logic to send reminder emails to users. Week 11 During the eleventh week, I worked on improving the security of the application by implementing input validation and sanitization. I also worked on implementing rate limiting to prevent brute force attacks. Week 12 In the twelfth week, I worked on implementing a feature to integrate with external APIs. I used the OpenWeatherMap API to retrieve weather data and implemented the logic to display the weather forecast on the application. Week 13 During the thirteenth week, I worked on writing unit tests and integration tests for the application. I used JUnit and Mockito to write tests and implemented the logic to test different modules of the application. I also documented the code and wrote user manuals to help users understand how to use the application.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值