C语言学习 第9天 【学会C语言就靠它了......】

一、指针与数组

1) 数组名就是数组的首地址(第一个元素的地址)。数组名是常量。

int a[10];
int b;
a = &b;//数组名是常量,不能被赋值
a++;//++给变量自身的值+1,a是常量,不能使用++ --  += -= *= /= %=

​ 指针变量是存放地址的变量。

2) 数组中的元素在内存中连续存放。

​ 地址的偏移运算,数组首地址+1得到谁的地址?数组中第二个元素的地址。

​ 可以通过首地址进行偏移运算,得到每个元素的地址。

#include <stdio.h>

int main()
{
	int a[5];//定义int类型的数组,5个元素
	int i;
	printf("a = %p\n", a);//打印数组名,数组名是数组的首地址,所以使用%p
    //遍历数组,打印每个元素的地址
	for(i = 0;i < 5;i++)
	{
		printf("%p\n", &a[i]);
        //观察打印结果,发现&a[0]和a是一样的,实际上他们俩就是一回事
        //观察发现给个元素的地址相差4字节,因为元素类型是int类型
	}
	return 0;
}

思考:既然数组名是地址,能不能偏移运算?能不能间接运算?

int main()
{
    int a[5];
    int i;
    for(i = 0;i < 5;i++)
    {
        printf("%p %p\n", &a[i], a+i);// &a[i]是i元素的地址,a+i也是i元素的地址
        *(a+i) = i;//a+i得到i元素的地址,*i元素的地址得到i元素本身,=给i元素赋值
        //等价于a[i] = i;
    }
    //遍历数组打印每个元素的值
    /*a[i]和*(a+i)是完全等价的,既[]这个运算符是二元运算,左值是地址,右值整数。
    	[]的算法是先将 地址(左值)+整数(右值) 得到一个新的地址,然后对新的地址进行间接运算,得到新地址			对应的对象本身。
    */
    for(i = 0;i < 5;i++)
    {
        printf("%d %d\n", a[i], *(a+i));//写两遍输出,是为了说明语法,a[i]和*(a+i)是完全等价的
    }
	return 0;
}

指针与数组基本用法示例:

当指针存放数组首地址的时候,我们说指针指向数组。

数组名的地址类型是?跟数组元素一个类型。

当指针指向一个数组时,指针需要定义成数组的元素类型!!!!!!!!!!

#include <stdio.h> 

int main( )
{
	int a[10] = {12,23,34,45,56,67,78,9,98,33};
	int *p;//p要指向数组a,由于数组a的元素是int,所以指针的类型也是int
	p = a;//将数组的首地址赋值给指针
	int i; 
	for(i=0; i<10; i++)
	{
        //p[i]运算得到数组a的i元素本身
        /*
        []是二元运算符,左值是地址,右值是整数
        算法:左值+右值得到一个新的地址,然后对新地址间接运算得到新地址对应的对象本身。
        p中的数据的a的首地址
        p[i]等价于*(a+i)
        */
        //p+i运算得到数组a的i元素地址
		printf("%d %p\n", p[i], p+i);
    }
	return 0;
}

指针与数组编程示例1:

使用指针指向一个int数组,使用指针将数组元素求和。输出最终结果。

这道题只是为了练语法。

#include <stdio.h>

int main()
{
	int a[5] = {1,2,3,4,5};
	int* p = a;//使用数组名初始化指针p
	int i, sum = 0;
	for(i = 0;i < 5;i++)
	{
		sum += p[i];//p[i]运算得到数组的i元素本身
	}
	printf("%d\n", sum);
    return 0;
}

指针与数组编程示例2:

定义字符数组,将字符串放在数组中,定义指针指向数组,使用指针输出数组中保存的字符串。

#include <stdio.h>

int main()
{
    char a[]="China";
    char *p;
    p=a;
    while(*p!='\0')
    {
        printf("%c",*p);
        p++;
    }
    return 0;
}

C语言对字符串的处理有两个要素:

  1. 字符串首地址 作为字符串的开始

  2. ‘\0’作为字符串的结尾

    C语言因为对于字符串的 所有 的操作,都是从字符串的首地址开始,到’\0’结束。

    使用%s需要传给它字符串的首地址,它会从首地址开始输出,直到’\0’为止。

    int main()
    {	
        char buf[] = "hello world";	//使用字符串常量初始化数组
        char* p = buf;//使用指针指向字符数组buf
        printf("%s\n", p+6);//world p+6作为输出字符串的首地址,是w的地址,所以从w开始输出,到\0结束
        printf("%s\n", buf);//hello world buf是h的地址
        return 0;
    }
    

    关于字符串操作函数

    #include <string.h>
    /*
    strcpy的函数原型
    参数1:字符数组的首地址
    参数2:字符串首地址
    char* strcpy(char* dst, const char* src);
    */
    int main()
    {
        char s1[100];
        char* s2 = "hello world";
        strcpy(s1, s2+6);
        printf("%s\n", s1);//world
        
        char* p1 = "hello";
        char* p2 = "hello";
        printf("%d\n", strcmp(p1, p2+1));//1  比较的是hello ello两个字符串,根据字符串比较的原理(第一个不相等字符的ascii码值),实际比较的是h e的大小,所以h>e输出1
    	return 0;
    }
    

数组作为函数参数示例:

数组作为函数的参数,一般传递两个参数。目的是为了在被调函数中操作主调函数中定义的数组。

1 数组的首地址

2 数组的长度

#include <stdio.h>
/*
形参为了传递数组
参数1:数组首地址
参数2:数组元素个数(数组长度)
*/
void print_array(int *p, int n) 
{
	int i; 
	for(i=0; i<n; i++)
	{
		printf("%d, ", p[i]);//使用指针遍历数组
	}
    printf("\n");
}

int main()
{
	int a[6] = {1,2,3,4,5,6};
	int b[8] = {6,6,6,6,6,6,6,6};
	print_array(a,6);//实参是数组名和数组的元素个数 1 2 3 4 5 6
	print_array(b,8);//实参是数组名和数组的元素个数 6 6 6 6 6 6 6 6
	return 0;
}

sizeof计算数组长度的错误理解!

sizeof计算数组元素个数,没有用!!!

#include <stdio.h>

void printArr(int* p)
{
	//sizeof(p) 这里计算的是指针的长度
	//所以使用sizeof计算数组长度,是扯淡,在
	//已知数组的地方不需要算,在未知数组长度的地方算不了
	printf("printArr:%d\n", sizeof(p)/sizeof(int));
}

int main()
{
	int a[10];
	//sizeof(数组名)计算数组占内存的大小
	//sizeof(类型名)计算元素占内存大小
	//数组占内存大小/元素占内存大小=数组元素个数
	//但是,我们为什么要计算数组的元素个数?
	//在定义数组的时候,长度必须以常量形式写清楚,我们没有计算数组长度的需求
	printf("main:%d\n", sizeof(a)/sizeof(int));
	printArr(a);
    return 0;
}

指针与数组编程示例3:

编写函数fun:从n个学生的成绩中统计出低于平均分的学生人数,和平均分。

我们一般是使用返回值将函数产生的结果返回给主调函数,但是,返回值只能返回一个值,如果函数产生多个结果,仅凭返回值是不够用的。
所以我们经常使用指针类型的形参,将结果写给主调函数中的变量。

由返回值返回人数,平均分存放在形参 ptr_aver 所指的存储单元中(即平均分由参数返回)。

ptr_aver 称为输出参数。参数的目的是为了让被调函数将数据传给主调函数。

scanf就是我们遇到过的输出参数的典型应用。

int a;
scanf("%d", &a);//将被调函数scanf中读取到的值写给主调函数中的变量a

//返回值int类型,表示低于平均分的人数

//参数1和参数2是传数组给被调函数

//参数3:输出参数,用来将平均分写给主调函数中的变量

int getAvgAndNum(int* arr, int n, float* ptr_aver);

#include <stdio.h>

int getAvgAndNum(int* arr, int n, float* ptr_aver);

int main()
{
	int a[] = {1,2,3,4,5,6,7,8,9,10};
	float aver;
	int count = getAvgAndNum(a, 10, &aver);
	printf("%d %f\n", count, aver);
    return 0;
}

int getAvgAndNum(int* arr, int n, float* ptr_aver)
{
	float sum = 0;
	int i;
	for(i = 0;i < n;i++)
	{
		sum += arr[i];
	}
	*ptr_aver = sum/n;//除法中只要有一个数值小数,就是小数相除
	//float a = 20/8;//a值2.000,因为20/8得整数2,将整数2赋值给a
	int count = 0;
	for(i = 0;i < n;i++)
	{
		if(arr[i] < *ptr_aver)
		{
			count++;
		}
	}
	return count;
}

指针与数组编程示例4:

编写函数,实现字符串追加 功能。但不能调用字符串函数;

char s1[20] = “hello ”;

char s2[] = “world”;

调用函数后,将world放到s1数组的”hello ”后面

知识点:当数组存放字符串时,作为函数的参数只需要传递首地址,因为字符串有’\0’作为结尾。

不允许使用C语言的字符串处理函数!!!

void mystrcat(char* dst, char* src);

#include <stdio.h>

void mystrcat(char* dst, char* src);

int main()
{
	char s1[100] = "hello ";
	char* s2 = "world";
	mystrcat(s1, s2);
	printf("%s\n", s1);
	return 0;
}

void mystrcat(char* dst, char* src)
{
	int i = 0;
	while(dst[i] != '\0')
	{
		i++;
	}
	//循环结束后i在dst的\0位置
	int j = 0;
	while(src[j] != '\0')
	{
		dst[i++] = src[j++];
	}
	dst[i] = '\0';
}

逻辑能写成并列的就不写成嵌套的!

太大的函数一定要拆分成若干小函数!

指针与数组编程示例5:

编写一个函数,将数组当做参数,将数组中n个数倒序。

12 34 56 32 6

6 32 56 34 12

#include <stdio.h>

void reverse(int* arr, int n);

int main()
{
	int a[10] = {1,3,5,7,9,0,8,6,4,2};
	reverse(a, 10);
	int i;
	for(i = 0;i < 10;i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

void reverse(int* arr, int n)
{
	int i;
	for(i = 0;i < n/2;i++)
	{
		int t = arr[i];
		arr[i] = arr[n-1-i];
		arr[n-1-i] = t;
	}
}

指针与数据编程示例6:

编写一个函数,可以对任意整型数组进行升序排序。

这题就属于封装固定的算法。

#include <stdio.h>

void sort(int* arr, int n);

int main()
{
	int a[10] = {12,34,56,78,54,32,2,6,8,43};
	sort(a, 10);
	int i;
	for(i = 0;i < 10;i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

void sort(int* arr, int n)
{
	int i, j;
	for(i = 0;i < n-1;i++)
	{
		for(j = 0;j < n-1-i;j++)
		{
			if(arr[j] > arr[j+1])
			{
				int t = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = t;
			}
		}
	}
}

指针与数据编程示例7:

对35选7进行封装

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void CreateWinnum();  //生成中奖号码
void UserInputnum();  //用户输入自己选中的号码
void PrintNum(int *arr);      //打印号码
int  ReplNum(int *arr,int n);      //判断生成和输入的号码是否合法
int  Wincount(int *Win,int *User);      //记录中奖号码个数
void printwin();    //打印中奖信息

int winnum[7]={0};  //存储生成的奖号
int usernum[7]={0};  //存储用户输入的号码
int WinCount=0;     //记录中奖号码个数

int main()
{
    srand(time(0));
    CreateWinnum();
    UserInputnum();
    PrintNum(winnum);
    PrintNum(usernum);
    Wincount(winnum,usernum);
    printwin();
    return 0;
}
//生成中奖号码
void CreateWinnum()
{
    for (int i = 0; i < 7; ++i) {
        winnum[i]=rand()%35+1;
        if( ReplNum(winnum,i) )
        {
            --i;
        }
    }
}
//用户输入自己选中的号码
void UserInputnum()
{
    printf("请用户输入所选号码:\n");
    for (int i = 0; i < 7; ++i) {
        scanf("%d",&usernum[i]);
        if(usernum[i]<0 || usernum[i]>36)
        {
            --i;
            printf("超出可选范围,请重新输入!\n");
        }
        else if(i>0 && ReplNum(usernum, i))
        {
            --i;
            printf("与前面号码重复,请重新输入!\n");
        }
    }
}
//打印号码
void PrintNum(int *arr)
{
    for (int i = 0; i < 7; ++i) {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
//判断生成和输入的号码是否在允许的范围内并且是否与前面的重复
int ReplNum(int *arr,int n)
{
    int count=0;
    if(arr[n]< 0 || arr[n]>35)
    {
        return 1;
    }
    else if(n>0)
    {
        for (int i = 0; i < n; ++i)
        {
            for (int j = i + 1; j < n + 1; ++j)
            {
                if (arr[i] == arr[j])
                {
                    count++;
                    break;
                }
            }
        }
        if(count)
        {
            return 1;
        }
    }
    return 0;
}
//记录中奖号码个数
int  Wincount(int *Win,int *User)
{
    for (int i = 0; i < 6; ++i) {
        for (int j = i+1; j < 7; ++j) {
            if(Win[i]==User[j])
                WinCount++;
        }
    }
}
//打印中奖信息
void printwin()
{
    if (WinCount)
    {
        int sum=10;
        for (int i = 0; i < WinCount; ++i) {
            sum*=10;
        }
        printf("恭喜您中了%d的大奖\n",sum);
    }
    else
    {
        printf("抱歉,很遗憾您没中奖!");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IbxkY3r1-1681033064774)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230205161741248.png)]

二、空指针和野指针

野指针:

空指针:

#include <stdio.h> 

int main()
{
	int* p;
	*p = 10;

	int* p1 = NULL;
	*p1 = 10;
	return 0;
}

#include <stdio.h>

int main()
{
	int a[5] = {1,2,3,4,5};
	int i;
	for(i = 0;i < 6;i++)
	{
		a[i] = i*-1;
	}
	return 0;
}

三、内存分配

1. 内存分区(变量可能被分配的内存分区)

栈:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量

堆:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量

静态:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量

2. 堆空间内存的申请与释放

#include <stdlib.h>

1) 申请内存: void *malloc(size_t size);

参数:申请内存的大小

返回值:无类型指针

2) 释放内存: void free(void *p);

参数:malloc申请内存的返回值

3. 使用注意事项:

1) malloc 和 free 成对去写。(malloc 申请的空间,使用结束后,要及时的free )

​ malloc不free会造成 内存泄漏。

2) malloc 申请内存,会成功或失败,如果成功,则可以使用申请来的堆空间。 如果失败,会返回 NULL

​ NULL就是0 空指针。内存不够就会失败。

3) 在堆内存被释放掉之后,及时将指针 p 归零,以防出现使用这块已经被释放掉的内存,造成不必要的风险。

示例:

#include <stdio.h> 
#include <stdlib.h> 
int main()
{  
	//int *p = (int *)malloc(10*sizeof(int));
    void* pv = malloc(10*sizeof(int));
    int* p = (int*)pv;
    if(p == NULL)
	{
		printf("error\n");
		return 0;
	}
	int i; 

	for(i=0; i<10; i++)
	{
		scanf("%d", &p[i]);
	}

	for(i=0; i<10; i++)
	{
		printf("%d, ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

四、位运算

为什么使用位运算?

1.左移和右移

左移示例

#include <stdio.h> 

int main()
{
	char a = 1;
	a = a<<1;
	printf("%d\n", a);
	a = a<<2;
	printf("%d\n", a);
	return 0;
}

右移示例

#include <stdio.h> 

int main()
{
	unsigned char a = 8;
	a = a>>1;
	printf("%d\n", a);
	a = a>>2;
	printf("%d\n", a);
	return 0;
}

2.按位取反

~

将变量中所有的位,1变成0,0变成1

#include <stdio.h> 

int main()
{
	unsigned char a = 0x01;
	a = ~a;
	printf("%#x\n", a);
	return 0;
}

3.按位与

& 双目运算 对应的位,有一个为0结果就是0

#include <stdio.h> 
int main()
{
	unsigned char a = 0xA5;
	unsigned char b = 0x8C;
	unsigned char c = a&b; 
	printf("%#x\n", c);
	return 0;
}

按位与的作用

1)给指定的位写0

unsigned char a;
a = a&~(1<<n);//将变量a的n位写0

#include <stdio.h> 

int main()
{
	unsigned char a = 0xA5;
	a = a&~(1<<5);
	printf("%#x\n", a);
	return 0;
}

2)读取指定位的值

if((a&1<<n) == 0)
{
	//a的n位是0
}
else
{
	//a的n位是1
}

读取示例

#include <stdio.h> 

int main()
{
	unsigned char a = 0xA5;
	int i;
	for(i = 0;i < 8;i++)
	{
		if((a&1<<i) == 0)
		{
			printf("a的%d bit is 0\n", i);
		}
		else
		{
			printf("a的%d bit is 1\n", i);
		}
	}
	return 0;
}

4.按位或

| 指定的二进制位,写1

a = a|1<<n;
#include <stdio.h> 

int main()
{
	unsigned char a = 0xA5;
	a = a|1<<3;
	printf("%#x\n", a);
	return 0;
}

5.按位异或

^ 指定位取反 对应的位,相同得0,不同得1

a = a^1<<n;  

1010 0101
0000 1000
----------
1010 1101

#include <stdio.h> 

int main()
{
	unsigned char a = 0xA5;
	a = a^1<<2;
   
	printf("%#x\n", a);
	a = a^1<<2;
	printf("%#x\n", a);
	return 0;
}

五、宏定义(无参)

为什么使用宏定义?

#define STUDENT_NUM 45
#define CHAIR_NUM	45
#define PC_NUM		45

for(i = 0;i < 45;i++)
{
 
}
#include <stdio.h> 
#define STUDENT_NUM 10 
int main()
{	
    int num = STUDENT_NUM;	
    printf("%d\n", num);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Super Mark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值