C语言基础知识
c概述
1,c特点:
优点:代码量小 速度快 功能强大
缺点:危险性高 开发周期长 可移植性不强
2,c的应用领域
系统软件开发:
操作系统:windows,linux,unix
驱动程序:主板驱动,显卡驱动,摄像头驱动
数据库:db2 oracle sql server
应用领域:办公(wps) 图像处理(ps) 嵌入式软件开发(智能手机,掌上电脑) 游戏开发(2d,3d)
3,c的32个关键字:auto break case char const continue default do double else
enum extern flaot for goto if int long register return
short signed sizeof static struct switch typedef unsigned union void
volatile while
c编程预备知识
1,cpu,内存条,硬盘,显卡,主板,显示器主机间的关系
操作系统将硬盘数据复制到内存条由cpu处理(cpu不能直接处理硬盘数据)处理后有声,有图像则经过声卡,显卡显示
2,hello world运行原理
程序通过编译器编译生成.exe文件,编译器请求操作系统调用cpu运行
3,什么是数据类型
基本数据类型
整数
整形 int 4
短整型 short int 2
长整型 long int 8
浮点型
单精度浮点数 float 4
双精度浮点数 double 8
字符
char 1
复合数据类型
结构体
枚举
共用体
4,什么是变量
变量的本质就是内存一段存储空间
5,变量为什么必须初始化
初始化就是赋值
6,如何定义变量
数据类型 变量名 = 要赋的值;
int i =3; 等价于 int i; i =3 ;
int i=3, j=5; 等价于 int i; int j; i=3; j=5;
7,什么是进制
逢几进几个,一次累加。
print用法:
%d表示十进制输出 //进制转换都可通过十进制 例:(47)10 转十六进制 47 / 16 = 2...15 就是2f,
%x或%x表示十六进制输出 //反之,十六转十进制 2=16 f=15 2*16+15=47
%0表示八进制输出 //十进制转八进制 47 / 8 = 5...7 就是57 反之 5*8+7=47
8,常量在c中的表示
整数:
十进制: 传统写法
八进制: 前面加0x或0x
十六进制:前面加0
浮点数:
传统写法:
float x = 3.3; //x值为3200
科学计数法:
float x=123.45e-2 //x值为1.2345
字符
单个字符单引号括起来 'a'
字符串双引号括起来 "abc"
9,常量以什么样的二进制代码存储在计算机中
整数:以补码形式转化为二进制代码存储于计算机中
实数:以teee754标准转化为二进制代码存储于计算机中
字符:本质与整除存储方式相同
10,代码规范
参考林锐《高质量c/c++》
11,什么是字节
存储数据单位 应件所能访问的最小单位
12,不同数据类型之间的相互赋值问题
大转小 小转大容易数据丢失
13,基本输入输出函数
print()输出
1,print("字符串");
2,print("输出控制符",输出参数);
3,print("输出控制符1 输出控制符2.......",输出参数1,输出参数2......);
4.print("输出控制符 非输出控制符",输出参数); //例:print("i = %d,j = %d" , i, j);
输出控制符包含以下:
%d %ld %c %f %lf %x或%x或%#x
测试%x %x %#x %#x的用法
# include<stdio.h>
{
int i = 47; 47 / 16 = 2 ....15 15 = f
print("%x\n",x); //输出结果:2f
print("%x\n",x); //输出结果:2f
print("%#x\n",x); //输出结果:0#2f
print("%#x\n",x); //输出结果:0#2f
}
为什么需要输出控制符
1,01代码可表示数据也可表示指令
2,若01代码表示数据,那么同样的01组合以不同的输出格式输出就会有不同的输出结果
scanf()键入数据至变量
两种用法:
scanf("输入控制符",输入参数); //例:scanf("%d",&i); //i表地址,&为取地址符
print("i = %d\n",i);
scanf("非输入控制符,输入控制符",输入参数); //scanf("m%d",&i); //输入前要加m,例:m123
14,运算符
取余 %
取余的运算对象必须是整数,结果是整数后的余数,其余数符号与辈出数相同。
//例;
print("%d %d %d %d %d %d\n",3%3,13%-3,-13%3,-13%-3,-13%-3,-13%23,3%5);
return;
结果为:0 1 -1 -1 -13 3
逻辑运算符
! &&(且) ||()或
&& 同真为真 一假为假 左边表达式为假,右边表达式不执行
|| 同假为假 一真为真 左边表达式为真,右边表达式不执行
15,if 语句
1,if的简单用法:
格式:
if(表达式)
语句
功能:表达式为真则执行,表达式为假则不执行。
2,if的范围:
if(表达式)
语句a;
语句b;
解释:只能控制a,不能控制b;加上“{}”才可以全部控制。
16,for()循环写100以内奇数平均值:
#include<stdio.h>
main(void){
int i, sum = 0, int = count;
float avg;
for(i = 0, i<101, i++){
if(i % 2 == 1){
sum+=i; //奇数和
++count; //奇数个数
}
}
avg = 1.0 * sum / count ; //奇数平均值
print("sum = %d\n", sum);
print("count = %d\n", count);
print("avg = %d\n", avg);
}
for()执行流程:
多个for循环的嵌套使用:
for(1 ; 2 ; 3 )
for( 4 ; 5 ; 6 ) //先执行内循环,内循环完成后才执行外循环。1245a632:执行流程。
a; //属于内循环 内循环5不满足时a一直执行到满足,满足后回到外循环,如果2不满足时,内循环则继续执行;满足则跳出到b执行。
b; //不属于内循环
例:斐波拉且数列
#clude<stdio.h>
main(void){
int n, f1 =2, f2 = 2, f3;
print("输入数列:");
scanf("%d", &n);
if(1 == n){
f3 =1;
}else if(2== n){
f3 = 2;
}else{
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
}
print("%d\n", f3);
return 0;
}
while()循环: 与for()单循环一样。
int 1;
while(2){
a;
3;
}
例:判断是否是回文。
#include<stdio.h>
main(void){
int val, m, sum=0;
m=val;
while(m){
sum=sum * 10 + m%10;
m /=10;
}
if(sum == val){
print("yes")
}
else{
print("no")
}
return 0;
}
17 自增/自减
自增:
前自增 ++i
后自增 i--
异同:
同:最终i都加1
异:前自增整体表达式的值是i加1之后的值
后自增整体表达式的值是i加1之前的值
18 do...while()求一元二次方程
#include<stdio.h>
#include<manth.h>
int main(void)
{
double a, b, c;
double delta;
double x1, x2;
char ch;
do{
print("请输入一元二次方程的系数:")
print("a=");
scanf("%lf", &a);
print("b=");
scanf("%lf", &b);
print("c=");
scanf("%lf", &c);
delta=b * b - 4 * a * c;
if ( delta > 0)
{
x1 = (- b +sqrt (delta)) / (2 * a);
x1 = (- b -sqrt (delta)) / (2 * a);
print("有两个解,x1 = %lf, x2 = %lf\n", x1, x2);
}else if (0==dalta){
x1 = x2 =(- b) / (2 * a);
print("有唯一解,x1 = x2 = %lf\n", x1, x2);
}else{
print("有无实数解);
}
print("是否继续<y/n>");
scanf(" %c", &ch); //%c前必须加空格,原因太复杂懒得说,自己百度。
} while('y' == ch || 'y' ==ch);
return 0;
}
19 break和continue
break:
在多层switch嵌套中break只能终止距离它最近的switch;
在多层循环中break只能终止距离它最近的那个循环;
在循环中break用于终止循环;
switch中break用于终止switch;
break不能直接用于if,除非if属于循环内部字句;
continue:
用于跳过本次循环余下的语句,转去判断是否需要执行下次循环;
例:
for(1;2;3){
a;
b;
continue; //执行完该语句后,会执行3,c和d被跳过不执行;
c;
d;
}
数组
为什么需要数组
数组的分类
一维数组
解决同类型数据的存储和使用
为n个变量连续分配存储空间
所有的变量数据类型必须相同
所有变量所占字节大小必须相同
初始化
完全初始化: int a[5] = {1 ,2 ,3 ,4 ,5};
不完全初始化: int a[5] = {1 ,2 ,3};
不初始化: int a[5];
清零: int a[5] = {0};
一维数组名不代表数组中的所有元素
一维数组名代表数组第一个元素的地址
二维数组
int a[ 3 ] [ 4 ]; 看作三行四列
int a[ i ] [ j ]; 表示第i+1行第j+1列;
int a[ m ] [ n ]; 该二维数组右下角位置的元素只能是 a [m-1] [n-1].
二维数组的输出:
int [2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int i, j;
for(i = 0; i < 2; ++i){
for(j = 0; j < 3; ++j)
print("%-5d",a[ i ] [ j ]);
print("\n")
}
多维数组
是否存在多维数组 不存在,因为内存是线性一维的1
n维数组可以当作每个元素是n-1维数组的一维数组
例;int a[ 3 ] [ 4 ] [ 5 ];
该数组是含有3个元素的唯一数组,只不过每个元素都是4行5列的二维数组;
函数
为什么需要函数
实现代码复用
有利于程序模块化
什么是函数
逻辑上:完成特定功能的独立代码块
物理上:
可以接收数据(也可以不接收)
对接收的数据进行处理
可以将数据处理的结果返回(也可以不返回)
总结:函数是个工具,它是为了解决大量类似问题而设计的。 //就是java中的方法。函数调用 == 方法调用
定义函数
函数的返回值 函数名(函数的形参列表)
{
函数的执行体;
}
1,函数定义的本质是详细描述函数之所以能够实现某个特定功能的具体方法;
2,函数返回值的类型也称函数的类型,因为如果,函数前的返回值类型 和 函数执行体中的 return 表达式;中的类型不同的话
则最终函数返回值的类型 以函数名前的的返回值类型为准;
例:
int f()
{
return 10.5; //因为函数的返回值类型是 int。所以f返回的是10而不是10.5;
}
3,return 表达式;的含义:
1,终止被调函数,向主函数返回表达式的值;
2,如果表达式为空,则只终止函数,不向主调函数返回任何值;
3,break是用来终止循环和switch的,return是用来终止函数的;
例:
void f(){
return; //终止被调函数,不向主函数返回值;
}
void f(){
return 10; //终止函数的,向主函数返回10;
}
函数分类
有参函数 和 无参函数
有返回值函数 和 无返回值函数
库函数 和 用户自定义函数
值传递函数 和 地址传递函数
普通函数 和 主函数(mian函数)
一个程序有且只能有一个主函数;
主函数可以调用其他函数,而其它函数不能调用主方法;
普通函数可以相互调用;
主函数是程序的入口,也是程序的出口;
例:求素数
#include<stdio.h>
bool isprime(int val){
for(int i = 2; i < val; ++i){
if( val % i == 0){ // 输入值对i能够整除说明不是素数,跳出。
break;
}else if (i == val){ //i是从2开始的,i++到与输入值相等说明,在i++中没有被整除,所以是素数;
return true; //判断出 是 返回ture 否 false
}else{
return flase;
}
}
}
int main( int m) {
int m;
scanf("%d", &m);
if (isprime(m)){
print("yes");
}else{
print("no")
}
}
声明函数注意事项
如果函数调用写在了函数定义前面,则必须加函数前置声明;
函数前置声明:
1,告诉编译器即将出现的若干字母代表的是一个函数;
2,告诉编译器即将出现的若干字母代表的函数的形参和返回值的具体情况;
3,函数声明是一个语句,末尾必须加分号;
4,对库函数的声明是通过 # include<库函数所在的文件的名字.h>来实现的;
例:void f(int); //带参函数 void f(void); //无参函数
形参和实参
个数相同 位置一一对应 数据类型必须相互兼容
合理设计函数思维
模块化编程,一个函数的功能尽量单一;
常用系统函数
double scrt (double x); //求x的平方根
int abs(int x); //求x的绝对值
double fabs(double x); //求x的平方值
推荐书籍:《turboc2.0》实用大全 ==javaapi
变量的作用域和存储方式
按作用分:
全局变量
在所有函数外部定义的变量叫全局变量
使用范围:从定义位置开始到整个程序结束
局部变量
在一个函数内部定义的变量或者函数的形参 都统称为局部变量
使用范围:本函数内部
局部函数和全局函数命名冲突
函数内部命名变量与全局变量重名时,局部变量会屏蔽全局变量。
按变量的存储方式分:
静态变量
自动变量
寄存器变量
例1,统计键盘输入数字内有多少是素数。主函数运算: //代码复用性不高;不够简单明了;
int main(void){
int val, i, j;
scanf("%d\n",&val);
for( i = 2; i < = val; ++i){ //从2---输入数的每个数挨个进行判断是否是素数
for(j = 2; j < i; ++j){ //对i++的每一个值进行判断是否是素数
if( 0 == i%j){
break;
}
if(j == i){
print("%d\n",i)
}
}
}
return 0;
}
例2,统计键盘输入数字内有多少是素数。 函数调用来写; //代码复用性提高,但若求多个数字中有哪些素数就不行了;
bool isprime( int m){
int i;
for( i =2; i <= m; ++i){
if(0 == m % i)
break;
}
if(m == i)
return true;
else
return false;
}
int main(void){
int val, i, j;
for( i = 2; i <= val; ++i){
if( isprime(i) )
print("%d\n", i );
}
return 0;
}
例3,求多个数字中有哪些素数;
bool isprime( int m){ //判断是否是素数,是 true 否 false
int i;
for( i =2; i <= m; ++i){
if(0 == m % i)
break;
}
if(m == i)
return true;
else
return false;
}
void traverseval ( int n){ //输出1-n之间的素数
int i;
for( i = 2; i < n; ++i){
if( isprime(i) )
print("%d\n", i);
}
}
int main(void){
int val;
char ch;
do{
traverseval (val);
print("是否需要继续输入:");
scanf(" %ch\n", &ch);
}whie( ' y ' == ch || ' y ' ==ch );
return 0;
}
指针
指针热身例子
int main(void){
int * p; //int * 表示p变量存放的是int 类型的地址,*p 是int类型
double * p; //同上
//理解:p是变量名,p变量的数据类型是 int * 类型;int * 类型就是存放int 变量地址的类型
int i = 3;
int j;
p = & i; /*
1,p保存了 i 的地址,因此 p 指向 i ;
2,p不是 i , i 也不是 p ;双方修改了值也互不影响;
3,如果一个指针变量指向了某个普通变量,则 *指针变量 就等同于 普通变量;
例:
如果 p 是个指针变量,并且存放了普通变量 i 的地址 则 p 指向了普通变量;
*p 就等同于 i
或者说:在所有出现 *p 的地方都可以替换成 i;
*/
j = *p; // *p 就是以 p 的内容为地址的变量;
print(" i = %d, j = %d\n", i, j ); //结果:i = 3, j = 3;
}
指针的重要性:
表示一些复杂的数据结构
快速的传递数据
使函数返回一个以上的值
能直接访问硬件
能够方便处理字符串
是理解面向对象语言中引用的基础
c语言的灵魂
指针的定义
地址
内存单元编号
从零开始的非负整数
范围:4g //cpu到内存条有三根总线,分别是控制,数据,地址;cpu连接内存条的线是地址线,一根线控制两个单元,0 1;地址总线有多少就是2的多少次方;
一般电脑32位,就是2的32次方。能控制2的32次方单元,能存储2的32次方字节,一个单元8位,2的32次方*8就是能控制的位;
1k = 2的10次方b;1m = 2的10次方kb = 2的20次方b;1g = 2的10次方mb = 2的30次方b;
那么内存为:2的32次方 = 2的30次方 + 2的2次方 = 4g;
指针
本质是操作受限的非负整数
指针就是地址,地址就是指针;
地址是内存单元的编号;
指针变量是存放地址的变量;
指针和指针变量是两个不同的概念;
注意:通常我们叙述是会把指针变量简称为指针,实际两者含义不同;
指针的分类
1,基本类型指针————————重点!重点!重点!
例:两个值互换
1,函数调用错误案例:
#include<>stdio.h
void swap_1(int i, int j){
int t;
t = i; i = j; j = t;
}
int main (void){
int a = 3;
int b = 5;
swap_1(a, b );
print("a = %d, b = %d\n", a, b);
return 0 ;
}
输出结果为:3 5
原因:首先主函数a,b是赋值了的。a,b 是实参,i,j是形参,是四个变量,就算把 i , j 改为 a , b 也还是四个变量(地址不一样)。
形参互换跟实参没有关系。
执行流程:主函数开始,检测到swap_1(int , int );去执行,先是给所有变量分配空间( i, j, t分配静态空间),将a, b 实参值传入形参 i , j。
形参进行互换, 互换结束后子函数释放空间,主函数运行到print("...");时就只剩下实参 a, b(子函数空间释放了,没了) ,原样输出,3 5;
2,地址错误案例:
#include<>stdio.h
void huhuan(int * p, int * q){ //接受实参数据的是 p, q ,而不是 *p, *q ,因为变量名为p,q
int * t; //同类型才可当互换的第三方变量
t = p; //这里换的是 p, q ,是地址,跟a , b没关系,换的不是值。
p = q; //* p , *q 才是a, b 的值,要换* p , *q 才能实现 3 5 的互换。
q = t;
}
int main(void){
int a = 3, b = 5;
huhuan(&a, &b); //这里必须是 &a, &b, 而不是 a, b 因为 a, b 是int 类型,p,q是int * 类型是地址,所以要用取地址符&a, &b。
print("a = %d, b = %d\n", a, b);
return 0 ;
}
输出还是 3 5 解答如注释
3,正确指针案例:
#include<>stdio.h
void huhuan(int *, int *); //参数可以不写
void huhuan(int * p, int * q){ // p是存放地址的变量,就是放的地址;*p是以p(p地址里面的值)的内容为地址的变量,也就是说*p存放是p地址的值;
int t; // 注意:p是int *类型, *p是int 类型;要互换*p,t (第三存储变量)就必须是int 类型
t = *p; //p->&a; q->&b; *p = a; *q = b; 互换的是a,b;所以这里是*p和*q的互换;
*p = *q;
*q = t;
}
int main(void){
int a = 3, b = 5;
huhuan(&a, &b);
print("a = %d, b = %d\n", a, b);
return 0 ;
}
关于函数和指针的见解(弹幕友军很nice)
函数作用:因为子函数并不是把主函数的值调过来,而是相当于复制了一份主函数的值进行运算最后返回结果,
因此子函数复制来的值和主函数的值并不是同一个值。在子函数复制来的值进行互换就不会影响到主函数值(本体);
指针作用:而指针在子函数中调用主函数的值(通过地址找到主函数本值)就是将主函数本值拿过来放在子函数中进行互换。
int * 类型变量p——>&a,就是变量p存储了a的地址及值,但是p只能表示地址不可表示a的值。
而*p就是以 int * p类型的内容(内容:地址的存放的值,例如p->&a,那么*p==a)为地址的变量
疑问:函数篇章时,判断素数也有函数调用,我疑问也是子函数调用了主函数变量,但是返回结果是正确的。按照上述不应该子函数运算后释放空间了,主函数也拿不到结果吗。
解答:主函数定义的变量是没有赋值的,所以不存在两个不同的变量值。
附注:
* 的含义
1,乘法
2,定义指针变量
int * p ; //定义了名为 p 的指针变量,int * 表示p只能存放一个int 变量的地址; 2,指针和数组
3,指针运算符
该运算符放在已经定义好的指针变量的前面
如果p是一个已经定义好的指针变量
则 *p 表示 以p的内容(内容:地址的存放的值,例如p->&a,那么*p==a) 为地址的变量
如何通过被调函数修改主函数普通变量的值
1,实参必须为该普通变量的地址
2,形参必须为指针变量
3,在被调函数中通过
* 形参名 = ......
的方式来修改主函数相关变量的值
2,指针和数组
1,指针和一维数组
一维数组名:一维数组名是一个指针常量,它存放的是一维数组第一个元素的地址;
下表和指针的关系
如果p是个指针变量,则 p[ i ] 永远等价于 *(p+i)
确定一个一维数组需要几个参数
两个:数组第一个元素的地址
数组长度
例:
#include<stdio.h>
void f (int * parr , int len ){
parr[ 3 ] = 88; //通过指针进行修改数组值
// for(int i ;i < len ; ++i)
// print("%d\n", parr[ 3 ]); //parr[ 3 ] == * parr[ i + 1];遍历输出数组值
}
int main(void){
int a[ 6 ] = {1, 2, 3, 4, 5, 6};
print("%d\n",a[ 3 ]);
f( a, 6); //a本身就是一个数组地址,a == a[ i ];a发给 parr 这两者是一样的(地址和内容)
a[ i ] == * a[ i + i] == parr[ i ] ==* parr[ i +1 ]
print("%d\n", a[ 3 ]);
return 0 ;
}
指针变量的运算
指针变量不能 相加 相乘 相除
如果两个指针变量指向的是同一块连续空间的不同存储单元,则这两个指针变量才可以相减
例:
int main(void){
int i = 5;
int j = 10;
int * p = &i, *q = &j;
// q - p; //无意义,无法相减。不在同一个连续的存储空间
int a [ 5 ];
p = &a [ 1 ];
q = &a [ 4 ];
print("p和q所指向的单元相隔%d个单元\n", q - p);
return 0 ;
}
输出为 3 个单元
3,指针和函数
4,指针和结构体
5,多级指针
int i = 10;
int * p = &i;
int ** q = &p;
int *** r = &q:
int **类型是存储 int *地址的类型,int ***类型是存储 int **地址的类型。以此推论。
例1:静态变量不能跨函数使用
void f (int ** q){
int i = 5;
//*q 等价于p p和**q都不等价于p 因为**q存放*p的地址,*q==p
*q = &i; //p = &i
}
int main(void){
int * p ;
f(&p); //p是int * 类型 ,&p是int **
print("%d\n",*p); //语法正确,逻辑有问题;
return 0 ;
}
输出 5
f函数终止后,为f分配的所有静态变量会全部释放(i,q是静态分配),p指向f函数中的i变量就已经不存在了(或者说i的访问权限已经返回给操作系统了,不能被使用),p没有权限访问i的内存空间,所以错了;
例2:动态内存可以跨函数使用
void f (int ** q){
*q / p(表示这里p也可以) = (int *)malloc(sizeof(int)); //sizeof返回该数据类型所占字符,p =*q
// q = 5; //error 这个p是地址不可以赋值
// *q = 5; //error *q =p
**q = 5; //true **q =*p
}
int main (void){
int * p;
f(&p);
printf("%d\n",*p); //*p 这里没有问题,*p指向的是上面动态分配的。动态分配是在堆里分配的,堆里分配的函数终止时内存不会被释放;
//函数终止本质是出栈,静态空间是在栈分配的,出栈空间就没了,f函数也没有free(q),所以f函数终止后*q仍然属于本程序,*p就可以访问;
return 0;
}
专题:动态内存分配
传统数组的缺点:
1,数组长度必须事先制定,且只能是整数,不能是变量;
例:
int a [ 5 ]; //ok
int len = 5; int a [ len ]; //error
2,传统形式定义的数组,该数组的内存程序员无法手动释放,在一个函数运行期间,系统为该函数中的数组所分配的空间
会一直存在,直到该函数运行完毕时,数组空间才会释放;
3,数组的长度一旦定义,其长度就不能再更改,数组的长度不能再函数运行的过程中动态的扩充或缩小;
4,a函数定义的数组,在a函数运行期间可以被其他函数使用,但a函数运行完毕后(函数运行完毕,空间释放,没东西了),
a函数中的数组将无法再被其他函数使用;
传统方式定义的数组不能跨函数使用;
为什么要动态分配内存
动态数组解决了以上四个问题;
传统数组也叫静态数组;
动态内存和静态内存的比较
静态内存是由系统自动分配,由系统自动释放
静态内存是栈分配
动态内存是由程序员手动分配,手动释放
动态内存是堆分配的
动态内存分配举例——动态一数组的构造 malloc是 memory(内存)allocate(分配)
例1:
#include<stdio.h>
#include<malloc.h>
int main(void){
int i = 5;
int * p = (int * )malloc(4); /*
1,使用malloc函数,必须加 malloc.h 头文件
2,malloc函数只有一个形参,且参数是整形
3,4表示请求系统为本程序分配4个字节
4,malloc函数只能返回首字节地址,但要加上类型强制转换,表示首字节地址的类型;
5,p本身所占的内存是静态分配的,p所指向的内存是动态分配的;
*/
*p = 5; //*p也是代表int变量,只是跟上面的 i 变量的内存分配方式不一样
free(p); //free(p)表示把p所指向的动态内存给释放掉。p本身内存是静态,程序员无法释放,只能有函数终止时系统释放;
}
例2:
#include<stdio.h>
#include<malloc.h>
int main(void){
int a[ 5 ]; //int 4个字节,本数组20个字节
int len;
int * parr;
int i;
//动态的构造一维数组
printf("请输入要输入的元素个数");
scanf("%d", &len);
parr = (int *) malloc (4 * len); //动态数组,其格式用法与上静态数组一致,长度为len
//动态数组赋值
for(i = 0; i < len; ++i)
scanf("%d",&parr[ i ]);
//数组输出
fo(i = 0; i <len; ++i)
print("%d", &parr[ i ]);
return 0;
}
结构体
为什么需要结构体
为表示一些复杂的事物,而普通的基本类型无法满足实际需求;
什么叫结构体
把一些基本数据类型组合在一起的一个新的复合数据类型,叫结构体;
如何让定义结构体
struct student{ struct student2{ struct {
int id; int id; int id;
char name; char name; char name;
char pwd; char pwd; char pwd;
}; 定义数据类型而非定义变量; }st2; }st3;
推荐第一种,就类似java的实体类,这里是结构体变量
怎么使用结构体变量
赋值和初始化
定义的同时可以整体赋初值: struct student st={1,"张三","123"}; //类似于java的new对象;
如果定义完后,则只能单个赋初值 struct student st2; st2.id = 1; st2.name = "张三"; st2.pwd = "123"
如何取出结构体变量中的每一个成员
1,结构体变量名.成员名
2,指针变量名—>成员名 在计算机会被转化成(*指针变量名). 成员名 的方式来执行(常用)
例:
struct student{
int aage;
float score;
char sex;
};
int main(void){
struct student st={10,66.6,'f'}; //初始化的同时定义赋值
struct student * pst = &st;
pst -> age =88; //(常用)
st . score = 66.6f; //66.6在c中默认double类型,如果希望一个实数是float类型,则必须在末尾加一个f或f,因此66.6是double,66.6f或66.6f是float类型;
printf("%d %f\n",st .age, pst ->score);
return 0;
};
pst ->sge 在计算机内部会转化成 ( *pst ).age; //pst => age == (*pst) .age == st.sge;
通过函数完成对结构体变量的输入和输出
#include<stdio.h>
#include<string.h>
void iputstudent(struct student stu);
void outputstudent(struct student ss);
struct student{
int age;
char sex;
chae name[100];
};
int main(void){
struct student st;
inputstudent(&st); //结构体变量输入 有修改,必须发送st的地址
printf("%d %c %s\n",st . age, st . sex, st . name);
outputstudent(st); //结构体变量的输出 可以发送st的地址,也可以发送st的内容,但是用指针可以减少内存耗费,提高执行速度;推荐指针
return 0;
}
void outputstudent(struct student ss){ //输出不修改,ss和st是一样的,这里的可以不用指针
printf("%d %c %s\n",ss . age, ss . sex, ss . name);
}
void iputstudent(struct student *pstu){ //指针变量无论它指向的地址占几个字节,但它本身只占4个(存放首字节地址4个),一个变量不管多大但是只用他的首字节地址表示
//什么样的数据类型意味着 (*指针变量) 指向这个变量占多少字节
// 例:double *p, x = 66.6; p = &x; p就指向x变量占8个字节;以此推理,现在的 student *pstu 类型就是指向的这个占105个字节。int *p 指向的4个;
(*pstu).age = 10;
strcpy(pstu ->name, "张三");
pstu ->sex = 'f';
};
输出:10 f 张三
/*
* //此函数无法修改stu,的值,原因是之前的函数调用问题;
* void iputstudent(struct student stu){ //数据变量名定义的,非malloc动态分配的,所以是静态的
* stu.age = 10;
* strcpy(stu.name, ""张三); //表示将“张三”拷贝到name字符数组里;而stu.name = "张三" ; error
* stu.sex = 'f';
* };
* 原因:这个函数的变stu与上面的st是两个变量,函数终止后会将给此函数分配的静态变量都释放掉,所以这里的stu进行操作对st无影响;
*/
结构体变量的运算
结构体变量不能先相加,相减,相互乘除
可以相互赋值
结构体变量和结构体指针变量作为函数参数传递的问题
推荐结构体指针变量作为函数参数的传递
冒泡排序
void sort (int * a, int len) {
int i , j , t ;
for( i = 0; i < len -1; ++i){
for( j = 0; j < len-1-i; ++j){
if(a[ j ] > a[ j+1]){
t = a[ j ];
a[ j ] = a[ j+1 ];
a[ a+1 ] = t;
}
}
}
}
int main(void){
int a[ 6 ] = {3,4,1,8,6,2};
int i;
sort(a, 6);
for(i = 0; i< 6; ++i){
printf("%d",a[ i ]);
}
printf("\n");
return 0;
}
举例:
动态构造存放学生信息的结构体数组
#include<stdio.h>
#include<malloc.h>
struct student{
int age;
float score;
char name[100];
}
int main(void){
int len;
struct student * parr;
int i;
//构建一维动态数组
printf("输入学生个数:\n");
printf("len = ");
scanf("%d", &len );
parr = (struct student *)malloc (len * size(struct student ));
//输入
for( i = 0;i < len;++i){
printf("输入第%d个学生",i+1);
printf("age = ");
scanf("%d", &parr[i].age);
printf("name = ");
scanf("%d", parr[i].name); //本身就是数组,name就已经是地址了,所以不用加&
printf("score = ");
scanf("%d", &parr[i].score);
}
//根据成绩排序
for(i = 0;i< len-1;++i){
for( j = 0;j <len - 1 - i; ++j){
if(parr[ j ].score < parr[j+1].score){ //降序 > 升序
t = parr[ j ];
parr[ j ] = parr[ j + 1];
parr[ j+ 1] = t;
}
}
}
//输出
for( i = 0;i < len;++i){
printf("输入第%d个学生",i+1);
printf("age =%d \n", parr[i].age);
printf("name =%s \n", parr[i].name);
printf("score =%f \n", parr[i].score);
}
return 0;
}
补码
原码
也叫 符号-绝对值
最高位0表示正 1表示负 ,其余二进制是该数字的绝对值二进制位
简单易懂,加减复杂,存在加减乘除,增加了cpu复杂度,0的表示不唯一
反码
反码运算不便也没有在计算机中应用
移码
表示数值平均平移n位,n称为移码量
主要用于浮点数的阶码的存储
补码
十进制转二进制
正整数二进制
除2取余,直至商为零,余数倒排
负整数而今制
先求与该负整数相对应的正整数的二进制码,然后所有位取反,末尾加一,不够位数时,左边补1(补都少个取决于什么类型)
例如:-3 正整数二进制码:011 所有位取反:100 末尾加一:101 不够位左加一(int 类型32位):左补29个1(省略29个1) 101
0转二进制
全是0
二进制转十进制
如果首位是0,则表示整数,按普通方法求;例 (0110)2
如果首位是1,则表示负整数
所有位取反,末尾加一,所得数字就是该负数的绝对值
注意:这里的所有位必须补满才能取反。例如:int 类型 1011取反,就要补全前面的29个1,(省略28个1),因为不补满取反的话,系统会自动给你补满,但是补的不是1,是0,那就成正整数了,结果就错了;
链表
数组和链表的优劣
数组:
优点:存取速度快
缺点:需要一个连续的很大的内存;插入和删除元素效率低;
链表:
优点:
插入删除元素效率高
不需要一个连续的很大的内存
缺点:查找某位置元素效率低
链表很难,首先要学好指针。这是属于数据结构的内容。当时还没学好指针呢(特么的老师讲没讲我都不知道),学的稀里糊涂的。
但是,世上无难事,只要肯放弃;所以链表不弄了,c基础完结了就。后来也学过数据结构,栈,队,链表,二叉树等等,但是忘了,难得看了头疼。
https://www.bilibili.com/video/av12907870 //数据结构教程链接
https://www.bilibili.com/video/bv1os411h77o?p=180 //c基础教程链接