初识C语言_Part 2(零基础超详解!)

初识C语言 (2)

#1. 关键字

前言C语言提供了丰富的关键字这些关键字都是语言本身预先设定好的用户自己是不能创造关键字的

1.1static关键字

1.1.1 static修饰局部变量

我们来看下边一段示例代码:

void test()
{
	int a = 5;
	a++;
	printf("%d ", a);
}
int main()
{
	int i = 0;
	while (i < 10)
	{
		test();
		i++;
	}
	return 0;
}

这段代码会打印10个6,为什么呢?因为a这个变量是定义在test这个函数内部的是个局部变量每次调用test函数时a变量会被重新创建并且被赋初始值为5再经过++操作变成6被打印当test函数调用完毕时a这个局部变量又会被销毁当下一次调用时a又会被重新创建并被赋初始值为5这样一直重复10次

假若我们用static修饰a变量呢?

void test()
{
	static int a = 5;
	a++;
	printf("%d ", a);
}
int main()
{
	int i = 0;
	while (i < 10)
	{
		test();
		i++;
	}
	return 0;
}

此刻,就会在屏幕上打印6-15.

原因:被static修饰的局部变量,该变量的空间直到整个程序运行结束(main函数执行完毕)才会被销毁。当第一次调用test函数时,a被创建赋初始值为5,++之后变成6被打印,但是这次结束调用时,a变量的空间并不会被销毁,并且a的值为6,当第二次调用test函数时a的值仍然为6,并且a的地址不会发生变化,经过++操作之后,a变为7……以此重复10次。

注意:被static修饰的局部变量在编译阶段就被创建了

但是为什么被static修饰之后局部变量的空间就变得长期有效了呢

解释:在c/c++中,内存空间被大致分为了三部分:

栈区:存放局部变量,形参,以及 具有临时作用的变量,栈区变量的特点是,进入作用域就创建,出了作用域就被销毁。
堆区:用于动态内存分配的(malloc,realloc,calloc)
静态区:存放全局变量,静态变量,静态区的变量的特点是创建之后直到程序结束才被销毁

所以:局部变量本来是存放在栈区的但是被static修饰的局部变量就被放在了静态区了static修饰局部变量改变了变量的存储类型使局部变量的生命周期变长了但是作用域不变直到程序结束才结束

1.1.2 static修饰局部变量

我们在一个程序中创建两个源文件:

test1.c

int g_val = 10;

test2.c

#include<stdio.h>
extern int g_val;
int main()
{
	printf("%d", g_val);//打印10
	return 0;
}

假如test1.c中的g_val被static修饰呢?那么程序将报错无法运行。

因为全局变量具有外部链接属性,所以可以在其他的源文件内使用。static修饰全局变量时,改变了全局变量的链接属性,由外部链接属性变成了内部链接属性。所谓内部链接属性,就是只能在自己内部源文件能看到。这个静态变量只能在自己的源文件内部使用,不能在其他的源文件内使用了 。

1.1.3 static修饰函数

test1.c

int Add(int x, int y)
{
	return x + y;
}

test2.c

extern int Add(int x, int y);
int main()
{
	int a = 10;
	int b = 10;
	int sum = Add(a, b);
	printf("%d", sum);//打印20
	return 0;
}

若Add函数被static修饰,那么该函数只能在该源文件内部使用。

因为static修饰函数,函数本身也具有外部链接属性,被static修饰之后,就变成了内部链接属性。使得函数只能在自己所在的源文件内部使用,不能在其他文件内使用。

1.2.struct关键字

当我们想要用C语言描述一个学生时,单靠C语言本身的数据类型很难做到。C语言本身提供的类型有点单一,所以这时就可以自定义一个类型,可以用struct关键字来定义。例如:

struct Student
{
	char name[10];
	int age;
	char sex[5];
};

此时我们就创建了一个结构体类型,结构体中的name,age,sex称之为结构体成员。struct Student称为结构体类型,这个struct Student 是自定义类型,int是C语言自带的类型,他们的用法类似,都能创建变量。接下来我们利用struct Student 创建一个结构体变量。如下:

int main()
{
	struct Student student = { "张山",19,"男" };
    struct Student student1 = { "李四",17,"男" };
	return 0;
}

此时我们创建了学生student和student1,我们可以这样理解,struct Student就相当于是个房间设计图纸,通过这个图纸我们设计出很多房间。在创建结构体的同时,我们对结构体成员进行初始化。注意初始化时要和结构体的成员相对应,例如:张山对应char name[10]; 19对应int age;男对应char sex[5];

那么我们如何访问这些结构体成员呢?这里就要提到之前的 . (结构体成员访问符),下面我们使用这个操作符打印一下结构体成员:

struct Student
{
	char name[10];
	int age;
	char sex[5];
};
int main()
{
	struct Student student = { "张山",19,"男" };
	struct Student student1 = { "李四",17,"男" };
	printf("%s %d %s", student.name, student.age, student.sex);//打印结果: 张山 19 男
	return 0;
}

1.3 typedef关键字

定义:typedef是类型重定义符号。可以为一个类型重命名。看示例代码:

注意typedef是类型重定义例如下边这段代码被重定义之后的用法和重定义之前的用法是一样的

unsigned long long a = 10;
int main()
{
	unsigned long long b = 20;
	printf("%d %d", a,b);
	return 0;
}

我们在写这段代码时,会发现unsigned long long这个类型名字很长,为了方便节省时间,我们通常对其进行类型重定义操作。例如:

typedef unsigned long long ull;//被重定义之后,ull也是一个类型名
unsigned long long a = 10;//这里a和b的类型是一样的
int main()
{
	ull b = 20;
	printf("%d %d", a,b);
	return 0;
}

改成这样就会方便很多。注意typedef使用完之后要在语句之后加分号。(就像第一行这样,分号不能少)。

1.4 define定义常量和宏

1.4.1 define定义常量

我们看之前的文章举过的一个例子:

//编译失败 报错
int main()
{
	const int a = 10;
	int arr[a] = { 0 };
	return 0;
}

这段代码会报错,注意:数组的括号内必须是常量因为被const修饰的变量本质上还是变量那么这里提到的define关键字就可以定义真正的常量。请看代码:

这里特别注意c99标准之前,数组的大小不能是变量。但在c99标准中引入了变长数组的概念,这时允许数组的大小是变量,但是不能直接被初始化,但是visual studio编译器不支持变长数组。但是有些编译器是支持的,例如:gcc编译器就支持c99中的变长数组。

注意使用define时,前面的#不要忘了哟

//成功运行
#define MAX 100
int main()
{
	int arr[MAX] = { 0 };
	return 0;
}

这里的由#define定义的就是常量#define不仅仅能定义数字,例如:

//成功运行
#define NAME "张山" //define定义字符串
int main()
{
	printf("%s", NAME);//注意以%s格式打印字符串
	return 0;
}

当然还能定义其他的类型,这里就不赘述了。

1.4.2 define定义宏

回忆一下之前讲过的函数部分,我们提到了Add函数,能完成两个数的相加,下面我们就用宏来实现类似的相乘的功能。

//语法格式 #define 宏名(宏参数)宏体
#define ADD(x,y) ((x)*(y))
int main()
{
	int a = 10;
	int b = 20;
	int sum = ADD(a+b, b);
	printf("%d", sum);
	return 0;
}

可以自己感受一下宏的用法,这里建议((x)*(y))这里分别给x和y的括号不要省略了,假若我们去掉,测试一下:

#define ADD(x,y) (x*y)
int main()
{
	int a = 10;
	int b = 20;
	int sum = ADD(a+b, b);
    //编译的时候会替换成: int sum = (a+b*b) 不是我们想要的运算顺序和运算结果
	printf("%d", sum);
	return 0;
}

我们会发现,这里就得不到我们想要的结果了,因为宏只是简单字面替换。

2 指针

2.1 引言

我们日常写的代码是会被加载到内存当中的,内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址,这些地址也称为指针

2.2 用法

而我们想要拿到一个变量的地址,就要利用&(取地址)这个符号,得到的就是内存中的地址.例如:

int a = 10;//创建一个int类型变量
&a//取出a的地址

那么我们取出来的地址该存放在哪里呢?这时我们就需要创建一个指针变量了,指针变量用于接收变量的地址,我们看下面一段示例代码:

int a = 10;
int * pa = &a;

这里具体说明一下第2行的意思,int * pa,这里的*说明pa是一个指针变量,而前面的 int 说明 pa 这个指针变量中存放的是 a 这个 int 类型数据的地址,一般而言,这里*前面的int要与变量a的类型相对应,但是也可以不用对应,后期会讲解的

2.3 指针变量的大小

指针变量是用来存地址的,而内存中的地址编码与电脑的内存中的真实存在的物理电位线有关,所以,==无论是什么类型的指针变量只要物理电位线是相同的,那么指针变量在内存中占用的内存的大小也就确定了。==一般而言,32位系统有32个电位线,64位系统含有64个电位线。

  • 在32位系统下,指针变量的大小是4个字节

  • 在64位系统下,指针变量的大小是8个字节

    这里我们就以整形指针为例做个测试:(当然其他类型的指针的结果也是类似的,自己动手去测试测试吧!)

    64位:

    int main()
    {
    	int a = 10;
    	int* pa = &a;
    	printf("%d", sizeof(pa));//
    	return 0;
    }
    

>

32位:

>

2.4 指针的简单应用

这里回忆之前的struct关键字,struct关键字可以用于创建一个结构体,我们看下面一段示例代码:

#include<stdio.h>
struct Student
{
	char name[10];
	int age;
	char sex[5];
};
void print1(struct Student t)
{
	printf("%s %d %s", t.name, t.age, t.sex);
}
int main()
{
	struct Student student = { "张山",19,"男" };
	print1(student);
	return 0;
}

print1函数是直接接受主函数传过来的student,再通过形参t进行打印。这种方法有个缺陷,就是当结构体的成员过多时,函数的形参的拷贝量比较大,占用的空间会比较大。而之前提到,指针变量的大小往往是4/8个字节,那我们能否直接将结构体的地址传递给函数呢?我们测试一下:

struct Student
{
	char name[10];
	int age;
	char sex[5];
};
void print2(struct Student* t)
{
	printf("%s %d %s", t->name, t->age, t->sex);
}
int main()
{
	struct Student student = { "张山",19,"男" };
	print2(&student);
	return 0;
}

这样也能成功打印结构的成员,但是注意这里代码第12行传参传递的是结构体的地址,所以在接收这个参数时,我们需要使用对应的指针变量进行接收,传递的是struct Student 类型,就需要使用struct Student* 类型接收。像这种情况,print2函数被调用时传递的参数是地址称之为传址调用

注意此处在函数内部使用的指针变量访问结构体成员,与之前的方法又略有不同。这里我们使用的是 -> 这是英文输入法下的-和>组合而成。

这里使用传址调用的好处是:形参占用的空间内存更小了,并且在内存更小的情况下我们仍能实现相同的功能。

2.5 指针的解引用操作

当我们拿到了指针的地址并且将其存放于指针变量中,我们怎样通过指针变量寻找到这个变量呢?这里就要用到解引用操作符*,看示例代码:

int main()
{
	int a = 10;
	printf("%d\n", a);//打印10
	int* pa = &a;
	*pa = 20;
	printf("%d\n", a);//打印20
	return 0;
}

运行这段代码我们发现,a变量的值被改变了。这里解释一下:这里指针变量pa存放的是a的地址,所以我们就可以在pa这个指针变量之前加一个*(解引用操作符)去访问a这个变量,所以这里*pa和a是等价的前提是pa的指针的类型和a的数据类型要对应例如:这里就是整形指针pa对应整形数据类型a。

完结:

本章的内容就到这里啦,若有不足,欢迎评论区指正,最后,希望大佬们多多三连吧,下期见!

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

这里是彪彪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值