- 定义一个函数, 要求能够在函数中修改传入变量的值
int num = 6;
printf("调用之前:num = %i\n", num);
// change(num);
change(&num);
printf("调用之后:num = %i\n", num);
return 0;
}
//void change(int value){
// 结论: 如果函数的形参是数组或者是指针,
// 那么就可以在函数中修改外界传入变量的值
void change(int *value){ // 相当于 int *value = #
*value = 888;
- 需求: 要定定义一个函数, 在函数中交换传入变量的值
void main(){
int num1=10,num2=20;
chang(&num1,&num2);
printf("num1=%i,num2=%i",num1,num2);
}
基本数据类型作为形参, 在函数内修改形参, 不会影响到函数外的实参,所以定义为指针变量,传进来地址
int chang(int *n1,int *n2){
int mid=*n1;
*n1=*n2;
*n2=mid;
}
- 需求: 要求定义一个函数, 可以同时返回两个变量的和,差,积,商
需要大家知道的是: 在C语言中, 默认情况下一个函数只能返回一个值
如果想让某一个函数同时返回多个值, 可以借助指针来实现
#include <stdio.h>
//int sum(int num1, int num2);
void test(int num1, int num2, int *res1, int *res2, int *res3, int *res4);
int main()
{
int a = 10;
int b = 20;
// int res = sum(a, b);
// printf("res = %i\n", res);
int d, e, f, g;
test(a, b, &d, &e, &f, &g);
printf("和 = %i\n", d);
printf("差 = %i\n", e);
printf("积 = %i\n", f);
printf("商 = %i\n", g);
return 0;
// printf("return 后面的语句\n");
}
/**先输出/**,然后按住Ctrl旁边的Fn+回车键,自动生成以下注释
* @brief test 可以同时返回两个变量的和,差,积,商
* @param num1 参与运算的第一个变量
* @param num2 参与运算的第二个变量
* @param res1 和
* @param res2 差
* @param res3 积
* @param res4 商
*/
void test(int num1, int num2, int *res1, int *res2, int *res3, int *res4){
*res1 = num1 + num2;
*res2 = num1 - num2;
*res3 = num1 * num2;
*res4 = num1 / num2;
}
注意点:return后面的语句执行不到
int sum(int num1, int num2){
// 注意点:
// return的作用是用于结束当前函数
// 只要函数执行到return, 就会立刻停止往下执行
// 所以return 后面不可以编写任何语句, 因为执行不到
return num1 + num2;
// return num1 - num2;
// return num1 * num2;
// return num1 / num2;
}
*/
- 多级指针
前面我们学习的指针我们称之为一级指针
* 什么是多级指针
* 指针中保存的又是其它指针的地址, 我们就称之为多级指针
* 如何定义多级指针
* 在要保存的指针变量的基础上加一颗星即可
* 例如: int *p; 如果想保持指针变量p的地址, 只需在定义时多加一颗星即可 int **pp;
*
* 访问的规则:pp
* 如果定义就如何访问, 例如: int *p; 访问 *p; 例如: int **pp; 访问 **pp;
*
* 定义指针的规律:
* 1.将要指向的变量的定义拷贝过来
* 2.再拷贝过来的代码的类型和变量名称中间加上一颗星
* 3.修改变量名称
int num=6//int num;num=6;
int *p=#//int *p;p=#定义指针规律的意思就是把int num;拷贝过来,在int和num之间加*,在中间靠近谁都没关系,然后改变num的名称为p
int **p2=&p;
printf("&num = %p\n", &num); // &num = 0060FEAC
printf("p = %p\n", p); // p = 0060FEAC
printf("*pp = %p\n", *pp); // *pp = 0060FEAC
printf("&pp = %p\n", &pp); // &pp = 0060FEA4
规律: 如果想通过多级指针获取某个变量的值, 那么是几级指针, 前面就写几颗星即可
注意点: 在企业开发中, 最多二级指针, 三级顶天了, 四级没讲过
- 指针和数组
int ages[3] = {1, 3, 5};
for(int i = 0; i < 3; i++){
printf("ages[%i] = %i\n", i, ages[i]);
}
数组名称保存的就是数组占用内存最小的那个地址
既然数组名称保存的就是地址, 而指针也是用于保存地址的, 所以指针也可以指向数组
int *p = &ages;
printf("ages = %p\n", ages); // ages = 0060FEA0
printf("&ages = %p\n", &ages); // ages = 0060FEA0
int *p = ages;// int *p = &ages
printf("p = %p\n", p); // 0060FEA0
结论: 如果利用指针保存数组的地址之后, 那么 p = ages = &ages;
- 要求你写出三种访问数组元素的写法
int ages[3] = {1, 3, 5};//第一种
printf("ages[0] = %i\n", ages[0]);
int *p = ages;//第二种
printf("p[0] = %i\n", p[0]);
printf("0[p] = %i\n", 0[p]);//第三种
- 指针遍历数组
int ages[3] = {1, 3, 5};
int *p = ages;
// printf("*p = %i\n", *p); // 1
// printf("*(p + 1) = %i\n", *(p + 1)); // 3
// printf("*(p + 2) = %i\n", *(p + 2)); // 5
// printf("*p = %i\n", *p++); // 1
// printf("*p = %i\n", *p++); // 3
// printf("*p = %i\n", *p); // 5
// printf("*p = %i\n", *(--p)); // 3
for(int i = 0; i < 3; i++){
// printf("ages[%i] = %i\n", i, ages[i]);
printf("ages[%i] = %i\n", i, *p++);
}
- 指针和字符串(用指针变量保存字符串)
指针和字符串
* 字符串的本质就是数组, 所以指针也可以指向字符串
* 正式因为如此, 所以定义字符串又多了一种方式
char str1[] = {'l', 'n', 'j', '\0'};
char str2[] = "lnj";//可以写成str2[i]
char *str4 = "lnj";//同样可以写成str2[i]
利用数组和指针定义字符串的区别:
* 1. 存储的位置不同
* 如果是通过数组定义的字符串, 那么存储在内存的栈中
* 如果是通过指针定义的字符串, 那么存储在内存的常量区中
*
* 2.由于在内存中存储的位置不一样, 所以特性也不一样
* 如果是通过数组定义的字符串, 我们是可以手动修改
* 如果是通过指针定义的字符串, 我们不用手动修改
char str[] = "lnj";
char *str = "lnj";
printf("str = %s\n", str);
str2[1] = 'T';
printf("str = %s\n", str);
*
* 3.由于在内存中存储的位置不一样, 所以特性也不一样
* 如果是通过数组定义的字符串, 每次定义都会重新开辟存储空间
* 如果是通过指针定义的字符串, 重复定义不会重新开辟存储空间
char str1[] = "lnj";
char str2[] = "lnj";
printf("str1 = %p\n", str1); // 地址不一样
printf("str2 = %p\n", str2);
char *str1 = "lnj";
char *str2 = "lnj";
printf("str1 = %p\n", str1); // 地址一样
printf("str2 = %p\n", str2);
- 接收和打印,字符串数组和指针的区别
1.接收字符串的时候, 只能通过字符数组, 不能通过字符指针
char str[10];
char *str;//程序编译是不报错,运行时出现问题,这样定义接收不到
scanf("%s", str);
rintf("str = %s\n", str);
2.如果函数中返回的字符串是通过数组创建的, 那么外界无法获取
如果函数中返回的字符串是通过指针创建的, 那么外界可以获取
char *res = demo();
printf("res = %s\n", res);
char* demo(){//返回值为指针类型,所以有星号
char str[] = "lnj";//函数外面接收不到
char *str = "lnj"
return str;//str保存的是常量区lnj\0的地址
/ 注意点: 学习了指针之后, 建议将过去形参的数组类型修改为指针类型
int ages[3] = {1, 3, 5};
test(ages);
printf("ages[1] = %i\n", ages[1]);
//void test(int nums[]){
void test(int *nums){//由于test(ages)传进来的就是地址,用指针更形象
nums[1] = 6;
- 字符串数组
字符串数组
// 字符串就是一个数组, 所以字符串数组就是一个二维数组
char str[][] = {//字符串数组表示方法
"lnj",
"abc",
"def"
};
// 字符串数组的第二种格式
char *str[] = {
"lnj",
"abc",
"def"
};
- 练习
1.实现strlen()函数
char *str = "lnj666";//此处注意字符串为char类型,形参处也为char类型
// char str[10] = {'l', 'n', 'j', '\0'};
// 注意点: strlen计算的是保存了多少个字符串
// 计算规则, 就是返回字符串中\0之前有多少个字符
int res = myStrlen(str);
printf("res = %i\n", res);
return 0;
}
int myStrlen(char *str){
// 1.定义变量记录当前有多少个
int count = 0;
while(*str++){
count++;
}
// while(*str++ != '\0'){ 方法二
// count++;
// }
// while(str[count] != '\0'){ 方法三
// count++;
// }
return count;
}
2.实现strcat()函数
注意点: 第一个参数必须足够大
//char *str1 = "lnj"; // 不能用于拼接,因为不能看出第一个字符空间大小
// char *str2 = "it666";
char str1[10] = "lnj";
char str2[10] = "it666";
myStrcat(str1, str2);
printf("str1 = %s\n", str1);
return 0;
}
void myStrcat(char *str1, char *str2){
方法一:
while(*str1){
str1++;
}
while(*str2){
*str1 = *str2;
str1++;
str2++;
}
*str1='\0';
}
方法二:
// 1.拿到第一个数组\0的位置
int index = 0;
while(str1[index] != '\0'){
index++;
}
// 2.将第二个数组添加到第一个数组末尾
int count = 0;
while(str2[count] != '\0'){
str1[index] = str2[count];
count++;
index++;
}
3.实现strcpy()
同样不能用指针定义字符串数组,
int main()
{
char str1[9] = "lnj";
char str2[10] = "it";
// strcpy(str1, str2);
myStrcpy(str1, str2);
printf("str1 = %s\n", str1);
return 0;
}
void myStrcpy(char *str1, char *str2){
while(*str2){
*str1 = *str2;
str1++;
str2++;
}
*str1 = '\0';
}
/*
void myStrcpy(char *str1, char *str2){
int index = 0;
while(str2[index] != '\0'){
str1[index] = str2[index];
index++;
}
str1[index] = '\0';
}
*/
4.实现strcmp()
int main()
{
// strcmp
char str1[9] = "125";
char str2[10] = "124";
// 两个字符串相同返回0
// 第一个参数小于第二个参数 -1 负数
// 第一个参数大于第二个参数 1 正数
// 如果前面的内容都相同, 第一个参数的个数小于第二个参数, -1 负数
// 如果前面的内容都相同, 第一个参数的个数大于第二个参数, 1 正数
// int res = strcmp(str1, str2);
int res = myStrcmp(str1, str2);
printf("res = %i\n", res);
return 0;
}
int myStrcmp(char *str1, char *str2){
// while(*str1 != '\0' || *str2 != '\0'){
while(*str1 || *str2){
if(*str1 > *str2){
return 1;
}else if(*str1 < *str2){
return -1;
}
str1++;
str2++;
}
return 0;
}
/*方法二:
int myStrcmp(char *str1, char *str2){
int index = 0; // 3
// \0 \0
while(str1[index] != '\0' || str2[index] !='\0'){
// 3 3
if(str1[index] > str2[index]){
return 1;
}else if(str1[index] < str2[index]){
return -1;
}
index++;
}
return 0;
}
*/
-
指向函数的指针
- 指针变量的作用: 专门用于保存地址
* 指向函数的指针
* 计算机也会给函数分配存储空间, 既然函数会分配内存空间,
* 所以函数也有自己的地址, 所以指针变量也可以保存函数的地址
*
* 经过前面的学习, 我们知道数组名保存的就是数组的地址
* 函数和数组很像, 函数名中保存的就是函数的地址
*
* 如何定义指针变量?
* 1.将需要保存变量的定义拷贝过来
* 2.在数据类型和变量名称中间添加一个*
* 3.修改变量名称
*
* 如何定义保存函数的指针变量
* 1.将函数的什么拷贝过来
* 2.在函数返回值和函数名称总监添加一个*
* 3.修改函数名称
* 4.注意点: 需要将*和变量名称用括号括起来
*
* 我们说过指向函数的指针和指向数组的指针很像
* 如果是数组, 我们可以直接将数组名称赋值给一个指针变量
* 如果是函数, 我们也可以直接将函数名称赋值给一个指针变量
*
* 如果一个指针指向了数组, 那么访问数组就有了三种方式
* 数组名称[索引];
* 指针变量名称[索引]
* *(指针编码名称 + 索引)
*
* 如果一个指针指向了函数, 那么访问方式也有多种方式
* 函数名称();
* ( *指针变量名称)();
* 指针变量名称();
- 指针变量的作用: 专门用于保存地址
int num;
int *p;
p = #
int ages[3] = {1, 3, 5};
int *p;
p = ages;
printf("ages[1] = %i\n", ages[2]);//三种方式访问数组
printf("p[1] = %i\n", p[2]);
printf("p[1] = %i\n", *(p+2));
test(); // 第一种方式
void (*funcP)();
funcP = &test;//或者 funcP = test;
(*funcP)(); // 第二种方式
void (*funcP)();
funcP = &test;//或者 funcP = test;
funcP(); // 第三种方式
void test(){
printf("test\n");
}
- 练习:定义指针指向这几个函数
void (*p1)();
p1 = say;
p1();
void (*p2)(int, int);
p2 = sum;
p2(10, 20);
int (*p3)(int a, int b);
p3 = minus;
int res = p3(10, 20);
printf("res = %i\n", res);
char* (*p4)();
p4 = test;
char* res2 = p4();
printf("res2 = %s\n", res2);
return 0;
}
void say(){
printf("Hello World\n");
}
void sum(int a, int b){
printf("res = %i\n", a + b);
}
int minus(int a, int b){
return a - b;
}
char* test(){
char* name = "lnj";
return name;
}
- 要求一个函数既可以计算两个变量的和, 也可以计算两个变量的差
// int res1 = sum(10, 20);
// printf("res1 = %i\n", res1);
// int res2 = minus(10, 20);
// printf("res2 = %i\n", res2);
int res3 = test(10, 20, sum);//把sum地址传给test
printf("res3 = %i\n", res3);
int res4 = test(10, 20, minus);
printf("res4 = %i\n", res4);
return 0;
}
注意点: 指向函数的指针,作为函数的形参时, 指针变量的名称, 就是形参的名称
// 如果指向函数的指针作为函数的参数, 那么这个可称之为回调函数
// 这里相当于, 给test函数传入了一个sum函数或者minus函数
// 然后又在test函数中调用了sum函数或者minus函
int test(int num1, int num2, int (*funP)(int, int)){
return funP(num1, num2);//把num1和num2的值传给sum函数
int sum(int num1, int num2){
return num1 + num2;
}
int minus(int num1, int num2){
return num1 - num2;
}
- 作业
1.要求从键盘输入一个字符串, 并且字符串中可以出现空格
- 2.将用户输入的字符串, 单词的首字母变成大写, 单词用空格划分
- hello world; --> Hello World;
- 3.将用户输入的字符串, 单词的首字母编程小写, 单词用空格划分
- Hello World; --> hello world;
- 4.要求定义一个函数, 既可以处理将首字母变为大写, 也可以处理将首字母变为小写
- 需要用到指向函数的指针
- 结构体
/*
* 什么是结构体?
* 结构体时构造类型的一种
*
* 构造类型前面我们已经学习过了数组:
* 数组的作用是用于存储一组相`同类型`的数据
* 结构体的作用是用于存储一组`不同类型`的数据
*
* 保存一个人的信息
* 姓名/年龄/身高 ...
* char *
* int
* double
*
* 如何定义结构体变量
* 1.先定义结构体类型
* 2.通过结构体的类型定义结构体变量
*
* 如何定义结构体类型?
* struct 结构体类型名称{
* 数据类型 属性名称;
* 数据类型 属性名称;
* ... ...
* };
*
* 如何定义结构体变量
* struct 结构体类型名称 变量名称;
*
* 如何访问结构体的属性
* 结构体变量名称.结构体属性名称;
*/
// 1.定义结构体类型
struct Person{
char *name; // name我们称之为结构体的属性名称
int age; // age也称之为结构体的属性名
double height; // height也称之为结构体的属性名称
};
// 2.定义结构体变量
struct Person p;
// 3.使用结构体变量
// int ages[3] = {1, 3, 5};
// ages[0] = 1;
// 格式: 结构体变量名称.结构体属性名称
p.name = "lnj";
p.age = 35;
p.height = 1.9;
printf("name = %s\n", p.name);
printf("age = %i\n", p.age);
printf("height = %lf\n", p.height);
return 0;
}
- 结构体变量初始化的几种方式
1.定义结构体类型
struct Dog{
char *name;
int age;
double height;
2.1先定义后初始化
// struct Dog dd;
// dd.name = "ww";
// dd.age = 1;
// dd.height = 1.5;
2.2定义的同时初始化
// struct Dog dd = {"ww", 1, 1.5};
注意点: 如果在定义的同时初始化, 那么初始化的顺序必须和结构体类型中的顺序一致
struct Dog dd = {.age = 1, .name = "ww", .height = 1.5};//也可以指定初始化,可以部分初始化.
3.特殊的初始化方式
数组只能在定义的同时完全初始化, 不能先定义再完全初始化
但是结构体既可以在定义的同时完全初始化, 也可以先定义再完全初始
// int ages[3] = {1, 3, 5};
// int ages[3];
// ages = {1, 3, 5};//数组不可以
// 企业开发不推荐这样编写
struct Dog{
char *name;
int age;
double height;
};
struct Dog dd;
dd = (struct Dog){"ww", 1, 1.5};//强制转换
printf("name = %s\n", dd.name);
printf("name = %i\n", dd.age);
printf("name = %lf\n", dd.height);
- 定义结构体变量的方式
1.先定义结构体类型, 再定义结构体变量
/*
struct Person{
char *name;
int age;
double height;
};
struct Person p1;
struct Person p11;
*/
2.定义结构体类型的同时定义结构体变量
/*
struct Person{
char *name;
int age;
double height;
} p2;
p2.name = "lnj";
printf("name = %s\n", p2.name);
struct Person p22;
*/
3.定义结构体类型的同时省略结构体名称, 同时定义结构体变量
// 匿名结构体
// 特点: 结构体类型只能使用一次
struct{
char *name;
int age;
double height;
} p3;
p3.name = "it666";
printf("name = %s\n", p3.name);
return 0;
- 结构体作用域(和变量相同)
- 结构体数组及初始化
struct Person{
char *name;
int age;
double height;
};
方法一:先定义在初始化
// struct Person p1 = {"lnj", 35, 1.90};
// struct Person p2 = {"zs", 22, 1.2};
// struct Person p3 = {"ls", 33, 1.4};
// struct Person p4 = {"ww", 56, 1.8};
数据类型 数组名称[元素个数];
struct Person ps[4];
ps[0] = p1;//ps[0] = {"lnj", 35, 1.90};下面也如此
ps[1] = p2;
ps[2] = p3;
ps[3] = p4;
方法二:定义同时初始化
struct Person ps[4] ={
{"lnj", 35, 1.90},
{"zs", 22, 1.2},
{"ls", 33, 1.4},
{"ww", 56, 1.8},
};
- 结构体内存分析
注意点:
* 给整个结构体变量分配存储空间和数组一样, 从内存地址比较大的开始分配
* 给结构体变量中的属性分配存储空间也和数组一样, 从所占用内存地址比较小的开始分配
*
* 注意点:
* 和数组不同的是, 数组名保存的就是数组首元素的地址
* 而结构体变量名, 保存的不是结构体首属性的地址
struct Person{
int age;
int score;
};
struct Person p;
printf("p = %p\n", p); // p = 00000077
printf("&p = %p\n", &p); // &p = 0060FEA8
printf("&p.age = %p\n", &p.age); // &p.age = 0060FEA8
printf("&p.score = %p\n", &p.score); // &p.score = 0060FEAC
//结构体在分配内存的时候, 会做一个内存对齐的操作
// 会先获取所有属性中占用内存最大的属性的字节
// 然后再开辟最大属性字节的内存给第一个属性, 如果分配给第一个属性之后还能继续分配给第二个属性, 那么就继续分配
// 如果分配给第一个属性之后, 剩余的内存不够分配给第二个属性了, 那么会再次开辟最大属性直接的内存, 再次分配
//以此类推
- 结构体指针
结构体指针
* 因为结构体变量也会分配内存空间, 所以结构体变量也有内存地址, 所以也可以使用指针保存结构体变量的地址
*
* 规律:
* 定义指向结构体变量的指针的套路和过去定义指向普通变量的一样
*
* 如果指针指向了一个结构体变量, 那么访问结构体变量的属性就有3种方式
* 结构体变量名称.属性名称;
* (*结构体指针变量名称).属性名称;
* 结构体指针变量名称->属性名称;
struct Person{
char *name;
int age;
double height;
};
struct Person per = {"lnj", 35, 1.9};//变量名为per
struct Person *p;//定义一个指针变量p,struct Person为结构体类型
p = &per;
printf("per.name = %s\n", per.name);
printf("per.name = %s\n", (*p).name);
printf("per.name = %s\n", p->name);
- 结构体的嵌套(提高代码的复用性)
struct Person{
char *name;
int age;
// 出生年月
int year;
int month;
int day;
// 死亡日期
int year2;
int month2;
int day2;
// 读幼儿园日期
// 读小学日期
// 读中学日期
所以可以把年月日写成一个结构体
// 1.定义了一个日期的结构体类型
struct Date{
int year;
int month;
int day;
};
// 2.定义一个人的结构体类型
struct Person{
char *name;
int age;
struct Date birthday;
};
struct Person p = {"lnj", 35, {2020, 12, 12}};
printf("name = %s\n", p.name);
printf("name = %i\n", p.age);
printf("name = %i\n", p.birthday.year);
printf("name = %i\n", p.birthday.month);
printf("name = %i\n", p.birthday.day);
- 结构体和函数(强调结构体作用域和变量相同)
1.虽然结构体是构造类型, 但是结构体变量之间的赋值
* 和基本数据类型赋值一样, 是拷贝
2. 注意点: 定义结构体类型不会分配存储空间
* 只有定义结构体变量才会分配存储空间
struct Person{
char *name;
int age;
};
struct Person p1 = {"lnj", 35};
struct Person p2;
p2 = p1;
p2.name = "zs";
printf("p1.name = %s\n", p1.name); // lnj
printf("p2.name = %s\n", p2.name); // zs
把p1的name更改写在函数中,更改后内存会释放掉,最终name值不变
struct Person p1 = {"lnj", 35};
printf("p1.name = %s\n", p1.name); // lnj
test(p1);
printf("p1.name = %s\n", p1.name); // lnj
return 0;
}
void test(struct Person per){
per.name = "zs";
}
- 共用体(企业开发中用的比较少)
共用体
*
* 共用体的格式:
* union 共用体名称{
* 数据类型 属性名称;
* 数据类型 属性名称;
* ... ...
* }
* 共用体定义的格式和结构体只有关键字不一样, 结构体用struct,共用体用union
*
* 共用体特点:
* 结构体的每个属性都会占用一块单独的内存空间, 而共用体所有的属性都共用同一块存储空间(同样先分配占用属性最大的字节数)
* 只要其中一个属性发生了改变, 其它的属性都会受到影响
*
* 应用场景:
* 同一个变量, 在不同的时刻,需要表示不同类型数据的时候, 我们就可以使用共用体
union Test{
int age;
char ch;
};
union Test t;
printf("sizeof(p) = %i\n", sizeof(t));
t.age = 33;
printf("t.age = %i\n", t.age); // 33
t.ch = 'a';
printf("t.ch = %c\n", t.ch); // a
printf("t.age = %i\n", t.age); // 97,,此时原来age的空间已经给ch用了
- 枚举(开发中经常用到)
枚举?
* 枚举用于提升代码的阅读性, 一般用于表示几个固定的值
* 所以还有一个名称, 叫做枚举常量
*
* 如果某些变量的取值是固定的, 那么就可以考虑使用枚举来实现
*
* 枚举的格式:
* enum 枚举类型名称{
* 取值1,
* 取值2,
* };
* 注意点: 和结构体,共用体不同, 枚举是用逗号隔开
*
* 规范:
* 枚举的取值一般以K开头,后面跟上枚举类型名称, 后面再跟上表达的含义
* K代表这是一个常量
* 枚举类型名称, 主要是为了有多个枚举的时候, 方便区分
* 含义, 你想表达的意思
*
* 枚举的取值:
* 默认情况下从0开是取值, 依次递增
* 也可以手动指定从几开始, 依次递增,例如KGenderMale = 9,从9开始递增
enum Gender{
KGenderMale , //打印输出为0,可写成male,但是 KGenderMale表达更清晰,编写的时候会提示输出
KGenderFemale, // 1,可写成female
// 2 ... ...
};
struct Person{
char *name; // 姓名
int age; // 年龄
enum Gender gender; // 性别
};
struct Person p1;
p1.name = "lnj";
p1.age = 58;
p1.gender = KGenderFemale;
struct Person p2;
p2.name = "周芷若";
p2.age = 88;
p2.gender = KGenderFemale;
- 枚举的作用域(和结构体类型的作用域一样, 和变量的作用域一样)
- 枚举的定义方式(和结构体相同)
2.1先定义枚举类型, 再定义枚举变量
enum Gender{
KGenderMale,
KGenderFemale,
};
enum Gender g1;
2.2定义枚举类型的同时, 定义枚举变量
enum Gender{
KGenderMale,
KGenderFemale,
} g2;
2.3定义枚举类型的同时,定义枚举变量 ,并且省略枚举类型名称
enum{
KGenderMale,
KGenderFemale,
} g3;
回顾局部变量和全局变量
* 局部变量:
* 概念: 定义在{}中,函数中,形参都是局部变量
* 作用域: 从定义的那一行开始, 直到遇到}结束或者遇到return为止
* 存储的位置: 局部变量会存储在内存的栈区中, 会随着定义变量的代码执行分配存储空间, 会随着作用域的结束自动释放
* 特点:
* 相同作用域类, 不能出现同名的局部变量
* 如果不同作用域内有相同名称的变量, 那么在访问时, 内部的会覆盖外部的(就近原则)
*
* 全局变量:
* 概念: 定义在{}外或者函数外的变量, 都是全局变量
* 作用域: 从定义的那一行开始, 直到文件末尾
* 存储的位置: 全局变量会存储在内存的静态区中, 会随着程序的启动分配存储空间, 随着程序的结束释放存储空间
* 特点:
* 如果有多个同名的全局变量, 那么也只会分配一次存储空间, 多个同名的全局变量共用同一块存储空间
- 变量修饰符(auto ,register,extern,static)
1.修饰局部变量(auto ,register)
- auto和register都是用于修饰局部变量的
auto int num = 9;
register int num = 9;
* 它们年的作用是修饰编程的存储特性
* auto: 特点就是告诉编译器, 局部变量离开作用域自动释放
* 默认情况下所有局部变量都是auto的, 所以这一句废话, 所以了解--> 忘记 / 即可
*
* register: 特点就是告诉编译器, 将局部变量存储到CPU寄存器中
* 好处就是访问的速度会更快, 但是在不同平台,不同编译器下, 会做不同的优化,寄存器存储空间非常有限,不一定能够存储进去, 所以还是一句废话, 所以了解 -->
2.static
2.1static对局部变量的作用
如果利用static修饰局部变量, 那么会将局部变量的存储区域从栈区移动到静态区
* 静态区只有程序结束才会释放
void calculate(int r){
// PI使用的概率非常大, 如果是一个局部变量的话, 每次调用都会重新开辟存储空间, 这样性能不好
// 如果PI是static的变量, 那么只会开辟一次, 那么性能就会好很多
static double pi = 3.1415926;
return r * r * pi;
}
2.2 static对全局变量的作用
* 定义一个内部的全局变量,
* 1.该变量只能在定义的文件中使用, 不能在其它文件中使用,例:在dashen.c文件中
* 2.并且该变量会独占一块内存空间
*
* 全局变量的特性:
* 可以定义多个同名的全局变量, 多个同名的全局变量共享一块内存空间
* 哪怕不是同一个文件中的同名全局变量, 也会共享同一块内存空间
* 问题:
* 这样是不是会导致数据混乱,因此引入static,在其他文件中无法使用
*
* 注意点:
* 局部变量如果没有初始化, 里面存储的是垃圾数据
* 全局变量如果没有初始会, 系统会自动初始化为0
2.3 static修饰函数
*代表这事一个内部函数, 只能在当前文件中使用
* 如果一些内部函数不想提供给外界使用, 那么就可以给函数添加一个static
*static必须写到函数的实现中才有效, 不能写到函数的声明中
* 并且如果一个函数已经被声明为static的了, 那么在.h文件中就不要编写该函数的声明了
3.extern
- extern对局部变量的作用
* extern用于声明一个变量, 声明变量并不会开辟存储空间
* extern一般用于全局变量, 至今没见过有人用extern来修饰局部变量
*
*
* extern对全局变量的作用
* extern用于声明一个变量, 声明变量并不会开辟存储空间
* exter只能用于全局变量, 不能用于局部变量
*
* 原因:
* 局部变量, 只有执行到那一行代码才会分配存储空间, 所以哪怕声明了 ,但是在使用时还是没有分配, 所以还是不能存储数据
* 全局变量, 会随着程序的启动分配存储空间, 所以只要声明了, 使用时已经分配好了存储空间, 一定能够使用, 一定能够存储数据
*
*
如果利用extern修饰函数, 代表这是一个外部函数, 其它文件中也可以使用
* 注意点: 默认情况下所有函数都是外部函数, 所有的函数都可以在其它文件中访问, 所以extern是一个废物
extern必须写到函数的实现中才有效, 不能写到函数的声明中
{extern int num;
num = 998;
printf("num = %i\n", num);
return 0;
} int num;
- 企业开发中大部分都是多人开发, 多人开发就是多个人一起写一个项目,所以在企业开发中, 都是多人同时操作多个不同的文件
例如: 现在有两个员工
* 一个是菜鸟, 一个是大神
* 调用大神写好的代码
* 编写主要的业务逻辑代码
*
在企业开发中如何进行多人开发
* 一般情况下会将(函数的实现), 编写到.c文件中, 同时会将.c文件中需要暴露给外界使用的方式名称的声明写到.h文件中
* 为什么编写了.c文件还需要编写一个.h文件, 原因很简单, (函数的实现)是你编写的, 那么函数的作用,形参你最了解, 所以应该由你来编写
注意:
* 在企业开发中, 其它人不需要关系函数具体是如何实现的, 只需要关心如何使用这个函数即可
* 所以(函数的实现)和声明都应该让同一个人来完成
*
* main函数中一般为主要业务逻辑,不能有太多冗余代码
* #include的作用:
* 将后面指定文件中的内容拷贝到当前文件中
* <>从系统的环境变量中去拷贝, 一般情况下只有用到系统函数才使用<>
* " "从指定的路径中去拷贝, 一般情况下使用同事/自己编写的.h文件都用""
#include "ds.h" // 导入大神编写的.h文件,例:#include "D:\WWW\ds.h"
本质就是将大神编写的.h文件中的代码拷贝过来