结构体
定义:结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同的变量。
struct——结构体关键字 stu——结构体标签 struct stu——结构体类型
struct stu
{
//成员变量——可以是标量,数组,指针,甚至是其他结构体
short age;
char sex[6];
char name[10];
} s1, s2, s3;//s1,s2,s3是三个类型为struct stu的全局变量
int main()
{
struct stu s = { 20, "male","张三" };//s是struct stu的局部变量 初始化——结构体在创建的同时对其进行初始化
return 0;
}
typedef struct tea//使用typedef可以将一个较长的类型名,定义成一个我们自己喜欢的一种新的类型名
{
}sta;
int main()
{
sta tea;//sta为类型名,tea为变量名
return 0;
}
嵌套结构体的初始化
struct S
{
int a;
char* p;
};
struct M
{
struct S s;
char arr[20];
double d;
char b;
};
int main()
{
char arr[20] = { "hello bit" };
struct M m = { {20,arr},"hello world",3.14,'a' };
printf("%s\n", m.s.p);
printf("%d\n", m.s.a);
printf("%c\n", m.b);//%s——打印字符串型 %c——打印字符型
printf("%s\n", m.arr);
printf("%lf\n", m.d);
return 0;
}
栈的数据结构特点:(从栈顶输入,从栈顶输出。)先进的后出,后进的先出。 插入一个元素:压栈 删除一个元素:出栈
在进行对结构体的函数传参时,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销就比较大,所以会导致性能的下降。
所以传址调用优于传值调用(形参是实参的一份临时拷贝,若实参过大,将导致运行时有很大的空间浪费。而传址则只将首元素地址传给函数,使得空间利用率更高
程序运行更便捷)
eg:
typedef struct stu
{
int arr[10];
char name[20];
}stu;
void change(stu* p)
{
p->arr[1] = 5;//p为一个类型为stu的指针变量
printf("%s\n", p->name);
}
int main()
{
stu ar = { {1,2,3,4,5,6,7,8,9,10}, "张三"};
stu* p = &ar;
change(p);
printf("%d", ar.arr[1]);
return 0;
}
c语言调试技巧
bug——一种导致计算机运行出现错误的一种dongx
调试——是一种减少或者消除计算机程序或电子仪器设备中程序错误的一个过程
/*
调试的步骤:
1、发现程序错误的存在
2、以调隔离和消除等方式对错误进行定位
3、确定错误产生的原因
4、提出纠正错误的解决办法
5、对程序错误予以改正,重新测试
*/
Debug——通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序
Release——称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好的使用(不能调试)
/*快捷键:
F5:启动调试,经常与断点(F9)联系起来,常用来快速运行(移动)到断点处。
调用堆栈:查看调用的情况,即函数是从什么时候开始调用的,怎么调用的。
*/
以下代码是用来求1!+2!+3!+.....+n!的。要求:经过调试,找出出现的问题
int main()
{
int i = 0;
int n = 0;
int ret = 1;
int sum = 0;
scanf("%d",&n);//输入3 结果应为9
for (i = 1; i <= n; i++)
{
int j = 0;
for (j = 1; j <= i; j++)
{
ret *= j;
}
sum = sum + ret;
}
printf("%d\n", sum);//输出结果为15
return 0;
}
经过调试中的监视窗口发现,ret在计算时未初始化成1。因此正确的代码应为:
int main()
{
int i = 0;
int n = 0;
int sum = 0;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
int j = 0;
int ret = 1;//在i每次进行循环时,都应将ret初始化成1
for (j = 1; j <= i; j++)
{
ret *= j;
}
sum = sum + ret;
}
printf("%d\n", sum);
return 0;
}
运行下列代码,运用调试技巧,研究程序死循环的原因
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i <= 12; i++)
{
arr[i] = 1;
printf("hehe\n");
}
return 0;
}
/*经过调试发现,当循环运行13次时,将arr[12]改成1的同时i也变成了1。通过对arr[12]和i取地址发现,两个变量的地址完全相同。因此改变arr[12]的同时也改变了i,因此会发生死循环。此代码最大的问题就是数组越界访问
知识点——栈区的默认使用:先使用高地址处的空间,再使用低地址的空间。
数组在创建空间时默认习惯:数组随着下标的增长,地址是由低到高变化的
*/
/*/优秀的代码:
1、代码运行正常
2、bug少(不单指语法bug,还有运行之后出现的bug等)
3、效率高
4、可读性高(别人能很容易读懂你的代码)
5、可维护性高
6、注释清晰
7、文档齐全
*/
/*常见的coding技巧:
1、使用assert——断言——如果assert结果为真,不执行assert。如果assert结果为假,则程序会进行抱错
2、尽量使用const
3、养成良好的编码风格
4、添加必要的注释
5、避免编码的陷阱
*/
创建一个自己的strcpy函数,并持续进行优化
#include<assert.h>
const char* arr2——*arr2是不能修改的左值,即指针变量所指的值不能被修改。但指针可以修改,即地址可以修改 (优化3)
char* const arr2——arr2是不能修改的左值,即指针变量不能被修改。但指针变量所指的值可以被修改
char* strcpy(char* arr1, const char* arr2)//const char* arr2——将arr2变成一个不可修改的值,以防在进行*arr1++ = *arr2++操作时,arr1与arr2的位置写反
{
char* ret = arr1;
assert(arr2 != NULL);//断言:若给arr2传参时误传了一个空指针,则程序会抱错 (优化1)
assert(arr1 != NULL);//断言:若给arr1传参时误传了一个空指针,则程序会抱错
while(*arr1++ = *arr2++)//先将*arr2的值赋给*arr1,再对arr1和arr2 +1,当++完成后判断,若*arr1='\0'(0),则退出循环 (优化2)
{
;
}
return ret;//ret返回的是arr1的起始地址。 若直接返回arr1,则返回值是++后的arr1的地址
}
int main()
{
char arr1[10] = "#########";
char arr2[10] = "bit";
printf("%s", strcpy(arr1, arr2));//函数返回值能充当另一个函数的参数,使函数的功能更加丰富 (优化4)
return 0;
}
int main()
{
char a[10] ="abcde";
char* p = &a[0];
printf("%c\n", *p);//打印字符时,用%c
printf("%s\n", p);//%s会读取到下一个指针直到遇上'\0'终止。因此,用printf打印字符串时参数为指针变量。
return 0;
}
模拟实现一个strlen函数
int my_strlen(const char* arr) //使用const修饰char* arr 优化(2)
{
int count = 0;
assert(arr != NULL);//保证指针的有效性 //优化(1)
while (*arr != '\0')
{
count++;
arr++;
}
return count;
}
int main()
{
char arr[20] = "abcdef";
int count=my_strlen(arr);
printf("count=%d\n", count);
return 0;
}
常见的错误类型:
/*编译型错误(语法错误):
解决方法:直接看错误信息(双击),解决问题
链接型错误:无法解析的外部符号
解决方法:看错误提示信息,主要在代码中找到错误信息中的标识符,然后定义问题所在。一般为标识符名未定义或拼写错误
运行时错误:
解决方法:运用调试,逐步定位问题*/