C语言学习笔记(2)

发布于:2025-02-11 ⋅ 阅读:(61) ⋅ 点赞:(0)

在学习前,需要有一定的C语言基础。不必很深入,只需要知道函数,头文件,指针,数组等的概念就可以,但并非0基础笔记。

由于写到后面,不好编辑了,决定分成多篇写,请按编号学习,或者直接看目录先。

说明:该文章来自本人学习时的笔记,使用的编译器是Visual Studio,如有错误,可以在评论区或者私信我纠正,谢谢 

13.指针基础

13.1内存模型

计算机最小的寻址单位:Byte字节,现在普遍都是一个字节是一个地址单位,1Byte=1bit

变量的地址:变量常常不止占用一个地址,所以变量的地址是首字节的地址

指针:地址

指针变量:存储地址的变量,往往指针变量也叫指针,一般可以理解是指针还是指针变量,(地址≠存储地址的变量)

13.2指针

int * p:存储int类型地址的指针

int的作用是表示指向对象的类型,也表示对象所占内存的大小,和解释那片内存的空间。

变量的类型是int *

int *p,q;         //p的类型是int *,q的类型是int

int *p,*q;        //p和q的类型都是int*

当时的条件过于苛刻,存储空间是非常宝贵的。

如今建议一个变量写一行,单独声明,提高可读性。

	int *p;
	int i=1;
	p=&i;
	printf("*p=%d  p=%d",*p,p);

*p 的 * 是解引用

访问 i 和 *p 的区别:但是实际上这个差距由于缓存的设计可以忽略,不必关注这个

i:直接访问内存空间,逻辑上访问一次

*p:间接访问,先访问p得到i的地址,再通过i的地址访问i,逻辑上访问内存两次

13.3野指针

野指针是表示没有初始化的指针,但是有初始值。如果直接对整数值赋值也是野指针,对嵌入式编程的时候可能会要进行整数赋值。

部分编译器在读取野指针的值的时候,可能会编译不过。对野指针解引用,会引发各种问题

千万不要放任野指针不管!

野指针就是不知道指向那块数据的指针

13.4空指针

不指向任何对象的指针,比如比较常见的,在使用链表的时候就可能需要使用空指针。

13.5指针赋值

指针变量赋值:指的是对指针赋值,把指针指向不同的地址

int i=0;
int *p ;
p = &i //这个是对指针变量的赋值

指针变量指向对象的赋值:对指针变量指向的地址所存的内容赋值,把p指针指向i,通过解引用,修改*p的值,来修改i的值。

int i=0;
int *p;
p=&i;
*p=1;//此时i的值被修改为了1

*p=*q的作用把*p的值,修改为*p的值,指针指向的地址并没有改变,但是指针指向地址所存储的值发生了变化。

13.6指针在函数中的应用

13.6.1指针作为参数

指针作为参数,传递的是地址,在被调函数中修改值,会修改主调函数中变量的值。

void foo (int *a){
    a=1;//主调函数调用以后,a的值会变成1
}

 因为C语言的特点,C语言只能有一个返回值,但是往往传入参数a可以作为返回值使用

在函数中直接使用a改变的是a的地址,对a解引用,直接对*a赋值才是修改a指向内存的值。

void min_max(const int arr[],int n,int *pmin,int *pmax){
	//arr[]作为传入参数,pmin和pmax可以作为传出参数返回值
	*pmax=arr[0];
	*pmin=arr[0];
	for(int i=1;i<n;i++){
		if (arr[i]<*pmin){
			*pmin=arr[i];
		}else if (arr[i]>*pmax){//比最小值小一定比最大值小,优化程序的性能
			*pmax=arr[i];
		}
	}
}

13.6.1指针作为返回值

指针作为返回值的时候,要注意函数的生命周期

int *foo(void) {
	int arr[] = {1, 2, 3, 4};
	return &arr[1];
}
int main(void) {
	int *p = foo();
	printf("*p = %d\n", *p);
	printf("*p = %d\n", *p);
	return 0;
}

 

两次并没有做任何操作,但是值却不一样。

如下图,在执行完foo以后,返回的是arr[1]的地址,第一次读取arr[1]的时候,foo还没有出栈,*p的值被foo修改为了2,所以第一次读取到的是正确的值,当第一次读取完以后,在第二次的时候,foo出栈了,也就是内存中不存在foo的栈帧了,*p指向的地址,所存储的栈帧发生了变化,故第二次读取到的值为不是2,由于编译器不一样可能读取到不同的值,一些编译器会把未初始化的值修改为0,一些编译器会直接读取指向内存的“脏数据”。

教训:千万不要返回指向当前栈帧的指针

13.7指针常量与常量指针

指针常量指的是指针指向的一个地址,对这个地址的内存,对这个指针来说解引用以后是一个常量,只有读权限,没有写权限。但是可以指向不同的地址

常量指针指的是指针指向一个地址,对这个指针变量来说,他不能修改指向的地址,但是可以修改这个地址的内存。

int i=10,j=20,k=30;
int*  p=&i;
*p=10;//*p对i的内存有写权限
const int* q=&i;//指针常量
//*q=10;   [Error] assignment of read-only location '* q'
//*q对i就没有写权限,i的内存可以修改,但不能通过p去修改
int* const c = &j;
*c = 10;//常量指针可以修改内存,但是不能修改其指向的地址
//c=&j;    [Error] assignment of read-only variable 'c'

常量指针对地址1有写权限,对地址2没有

指针常量对地址3没有写权限,对地址4有写权限

如果两个地方都加常量,则两个地址都没有写权限,const的本质是控制变量的权限

如const int * p = &i;

13.4指针与数组的关系

数组在作为参数的时候,会退化为指针

详细请看这篇文章12.4部分:C语言学习笔记(1)-CSDN博客

指针不是整数,指针+1的值实际上是指针向右偏移1的单位

用指针可以用来处理数组

	int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int sum = 0;
	for (int *p = &arr[0]; p < &arr[20]; p++) {
		//p不会显示越界,但是当p超过arr的边界的时候,会变成野指针,所以一定要确定p的范围
		sum += *p;
		printf("*p = %d ;sum = %d\n", *p,sum);
	}

在必要的时候,数组可以退化成指向他索引为0元素的指针

	int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	int sum = 0;
	for (int *p = arr; p < arr+10; p++) {
		sum += *p;
		printf("*p = %d ;sum = %d\n", *p,sum);
	}

指针的运算:

指针不是整数,可以相减,结果是整数,大小为偏移量的大小

但是不能相加、乘、除

指针可以进行比较运算

*可以和++,--相结合使用

区分(--的话类比):

*p++,*(p++):值为*p,副作用为p自增   (这个最常使用)

(*p)++:值为*p,副作用为*p自增

*++p,*(++p):值为*(p+1),副作用为p自增

++*p,++(*p):值为*(p+1),副作用为*p自增

14.字符串

14.1字符串字面值

字面值一定是常量,如“Hello world”,但常量不一定是字面值,如 const int i=1;

字符串的书写:

1.直接一行写完

2.使用跨行符

3.两个字符串直接只有空白字符,编译的时候是相邻的

printf("Hi NNNNNNNNN"
       "NNNNNNNNNNNN");

14.2字符串的内存模式

字符串的字面值是不可以修改的!

“ABC”在内存中的储存是ABC\0

printf("%c","ABC"[0]);

'\0'是空字符,在代码中占一个字符

14.3字符串字面值支持的操作

常量数组能支持的操作,字符串都可以支持

总纲:

1.C语言没有字符串类型

2.C语言中的字符串很依赖字符串数组的存在,遇到空字符结算

3.C语言中的字符串是一种逻辑类型

4.在C语言的字符串求长度时间复杂度是O(n),strlen的性能会很差

14.4字符串的语法结构

1.声明字符串变量并赋初始值

两个初始化方式是一样的,str2是str1的初始化方式是一样的,只是使用“语法糖”简写(对语法糖的表示可能不太准确,但就是这个·意思),只是机器帮你解决了str2转str1的步骤。

char str2[]={ 'h','e','l','l','o','\0' };//字符串数组初始化式,不要漏了'\0'
char str2[]="hello";//语法糖方式写

初始化的选择:

初始化字符数组使用str1的方式,初始化字符串使用str2的方式,提高可读性

2.一些细节

char str1[]="hello";//长度为6
char str2[10]="hello";//前面6个初始化为和str1一样,后面4个初始化为'\0'
char str3[5]="hello";//长度为5,但不是字符串,因为,没有以'\0'结尾

str的“hello”是字符串,在栈里面

p指向的“hello”是字面值,在代码段,是无法修改的 

	char str[] ="hello";//"hello":数组的初始化
	char* p = "hello";//"hello":字符串的字面值

14.5字符串的读写

输出p个字符,

printf("%.5s",str);//只输出5个字符

14.5.1scanf读写字符串

%s的匹配规则:

忽略前置空的字符,读取字符填入字符数组,遇到空白字符结束

scanf的缺点:

1.不能存储空字符

2.不会检查数组越界

14.5.2puts与gets

puts等价于printf(“s%\n”,str);//输出字符串的时候会自动换行

gets的原理是读取一行数据存入字符数组,并将换行符替换成空字符,gets也不会检查数组越界

14.5.3fgets(str,n)

和get差不多,但可以读取n个数据,会检查避免数组越界,会存储换行符,并在后面添加空字符。

14.6字符串变量的操作<string.h>

前面先说:掌握两个惯用法就好,在实际使用的时候,直接使用库里面的就好,不要自己去实现!!!

14.6.1 strlen(str)

读取一个字符串的长度,时间复杂度为O(n),不会计算空字符

实现:

方法1:

typedef int size_str;
size_str strlen(char *str){
    size_str str_len=0;
    while(*str!='\0'){
        str_len++;
        str++;
    }
    return str_len;
}

改进:领会字符串的方法

size_str strlen1(char *str) {
    const char* p=str;
    while(*p){   //遍历字符串惯用法
        p++;
    }

    return p-str;
}

14.6.2 strcpy(str1,str2)

复制字符串,但不会检查越界,返回值为str1

strncpy(str1,str2,n);//strcpy的安全版本,n是表示字符串长度(不包括'\0'),保证不越界

实现strcpy:

char* strcpy(char* str1, char* str2) {
    while(*str2){
        *str1 = *str2;//复制
        str1++;
        str2++;
    }
    *str1 = '\0';//不要忘记了最后补空字符
    return str1;
}

改进版本:

char* strcpy(char* str1, char* str2) {
    while (*str1 ++ = *str2 ++)
        ;//复制字符串惯用法,可以复制空字符串

    return str1;
}

实现strncpy:

char* strcpy(char* str1, char* str2) {
    while (*str1 ++ = *str2 ++)
        ;
    return str1;
}

14.6.3 strcat(str1,str2)

字符串拼接,str1一定要数组,而且str1的长度一定要可以存下拼接后的字符串,否则会越界

strncat(str1,str2,n),可以控制字符串长度,n的值为还可以拼接多少个字符(不包括‘\0’)

strncat实现

char* strcat1(char* str1, char* str2, int count) {
    char*p = str1;
    while (*str1) {
        str1++;
    }
    while ((count-->0)&&(*str1++ = *str1++))
        ;
    return p;
}

14.6.4 strcmp(str1,str2)

字符串比较,返回结果为str1-str2的值

比较规则:字典序,根据ASCII比较

实现:

int strcmp(const char* str1,const char* str2){
    while(*str1&&*str2){
        if(*str1 != *str2){
            return *str1-*str2;
        }
        str1++;
        str2++;
    }
    return *str1-*str2;
}

14.7字符串数组

在C语言里面,字符串数组是字符数组的数组,本质上就是字符二维数组,

字符串数组 char c[ ] ={"APPLE","APP","APPL","AP","APPL" }在内存中存储的内容如下

字符指针数组:用指针指向空间,只在末尾有一个‘\0’


网站公告

今日签到

点亮在社区的每一天
去签到