由ANSI标准定义的C语言关键字共32个:
*auto double int struct break else long switch
*case enum register typedef char extern return union
*const float short unsigned continue for signed void
*default goto sizeof volatile do if while static
数据类型关键字共12个
char、short、int、long、signed、unsigned、float、double、struct、union、enum、void
(1)unsigned :无符号的
用来声明一个无符号的变量。
unsigned char var; //var的范围:0~255
(2)signed :有符号的(可以省略不写)
用来声明一个有符号的变量。
signed char var; //var的范围:-128~127
(3)char :字符型
用来声明一个字符型变量。
占一个字节空间
char var;
(4)int :整型
用来声明一个整型变量。
C51:占两个字节空间,ARM:占四个字节
int var;
(5)float :浮点型
用来声明一个浮点型(实型)变量。
最多能表示到7个有效数据位。
占四个字节空间
float var;
(6)double :双精度型
//用来声明一个双精度实型变量。
//最多能表示到15~16个有效数据位。
//占四个字节空间 ,有的系统占八个字节
double var;
(7)short :短整型
用来声明一个短整型变量。
C51:跟int一样,ARM:占两个字节
short var;
(8)long :长整型
用来声明一个长整型变量。
ARM:跟int一样,C51:占四个字节
long var;
(9)void :空型
表示一个函数没有返回值,或者无形参。
void function(void);
(10)struct
用来声明一种结构体类型。
struct stu{
char sex;
int age;
float score;
struct stu *Next;
};
struct stu var;
(11)union
用来声明一种共用体类型。
该类型的变量所在空间的大小以其成员占最大的那个为准,
存入该变量中的值以程序中最后存入的数值为当前值
union non{
char sex;
int age;
float score;
};
union non var;
(12)enum
用来声明一种枚举类型。
规定枚举类型的变量,只能在限定的范围内取值
否则,编译会出现警告(达到数据安全的效果)
enum em
{a = 23,b,c,d = 56,e}; //其中b=24,c=25,e=57
enum em var;
1、循环控制(5个)
for、do、while、break、continue
C语言中有三种循环语句:while循环、do-while循环和for循环。continue和break的不同之处在于其是跳过本次循环进入下一次循环,而break则是直接跳出循环。
2、条件语句(3个)
if、else、goto
(1)if,else语句
要注意,else总是与离它最近的未被匹配的if相匹配。
if (表达式)
语句1;
else
语句2;
//多分支
if (表达式1)
语句1;
else if (表达式2)
语句2;
else
语句3;
//嵌套
if (表达式1) {
语句1;
if (表示式x) {
语句x;
}
else {
语句y;
}
}
else if (表达式2) {
语句2;
}
else {
语句3;
}
(2)goto语句
goto语句可以实现跳转,跳转到标签所在位置,但是不能跨代码块实现:
int main()
{
int i = 0;
START:
printf("[%d]goto running ... \n", i);
Sleep(1000);
++i;
if (i < 10) {
goto START;
}
printf("goto end ... \n");
system("pause");
return 0;
}
3、开关语句(3个)
switch、case、default
首先switch括号内表达式应该是整形表达式或者一个整型值
case则是对应的整型表达式的值或者对应的整型值,根据表达式的值转到对应的case语句。
break是用来跳出switch语句。每个case语句后都加上break。
而default是为了处理case语句中没有的特殊情况。
int main()
{
int day = 1;
switch (day) {
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期日\n");
break;
default:
printf("bug!\n");
break;
}
system("pause");
return 0;
}
4、返回语句(1个)
return
return用于终止一个函数,并返回其后面跟随的值。例如:
char* show()
{
char str[] = "hello world";
return str;
}
int main()
{
char* s = show();
printf("%s\n", s);
system("pause");
return 0;
}
auto、extern、register、static、typedef
(1)auto关键字
在代码块内部定义的变量是局部变量,而局部变量默认是用auto修饰的,但是一般都会省略。
#include
#include
int main()
{
auto int j = 0;
printf("%d\n",j);
system("pause");
return 0;
}
(2)extern关键字
extern变量称为外部存储变量,extern声明了程序中将要用到但尚未定义的外部变量。通常外部储存都用于声明在另一个转换单元中定义的变量。
一个工程是由多个c文件组成的。这些源代码文件会分别进行编译,然后链接成一个可执行的模块。把这样的一个程序作为一个工程进行管理,并且生成一个工程文件来记录所有包含源代码文件。如下图,main.c和time.c中的变量互相调用
(3)register关键字
register:这个关
键字请求编译器尽可能地将变量存在CPU内部寄存器中,而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对。可以想象,一个CPU的寄存器数量有限,也就那么几个或几十个,如果用户定义了很多很多register变量,会把CPU的寄存器占满。
同时register变量是不能被取地址的。因为取地址这种寻址操作只能在内存中进行。
#include
#include
#include "Test.h"
int main()
{
register int a = 27;
printf("a = %p\n",a);
system("pause");
return 0;
}
1.应当使用局部变量,因为全局变量会导致寄存器被长时间占用,可能会对计算机的状态造成影响。
2.应当在需要高频使用的时候用register关键字修饰局部变量。
3.如果要使用,也应当注意不能过多使用,因为寄存器数量是有限的,同时大都用来存储一些重要的状态信息
(4)static关键字
static变量为静态变量,将函数的内部变量和外部变量声明成static的意义是不一样的。
static的两个重要作用:
1、修饰变量。变量又分为局部变量和全局变量,但它们都存在内存的静态区。
(1)修饰全局变量:
static修饰的全局变量只能够在本文件内部被使用。
(2)修饰局部变量:
对于局部变量来说,其生命周期是在定义的代码块内部。但是如果用static修饰局部变量。其生命周期就会变成全局变量的生命周期。但是要注意,虽然其生命周期发生改变,但是其作用域却不发生改变。
#include
#include
#include
void AddOne(){
static int j = 0; //定义static变量
j = j + 1;
printf("%d\n", j);
}
int main(){
printf("第一次调用:");
AddOne();
printf("第二次调用:");
AddOne();
return 0;
}
可见static int j = 0;
只在第一次调用时初始化。
2、修饰函数。函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数是否会与其他文件中的函数同名。
(5)typedef关键字
typedef是用来对类型进行重命名的:
对一般类型进行重命名
对结构体类型进行重命名
对指针进行重命名
对复杂结构进行重命名
可参考第 八、typedef关键字
const、volatile、sizeof
(1)const关键字
const是constant的缩写,是恒定不变的意思,也翻译为常量和常数等。
const修饰的变量,不可直接被修改。但是却可以通过指针的解引用来进行修改。
const修饰指针变量分为几种情况:
const int *p;
// p 可变,p 指向的对象不可变
int const *p;
// p 可变,p 指向的对象不可变
int *const p;
// p 不可变,p 指向的对象可变
const int *const p;
//指针 p 和 p 指向的对象都不可变
同时const也可以修饰函数参数,const 修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
const int Fun (void);
也可以在另一连接文件中引用 const 只读变量:
extern const int i;
//正确的声明
extern const int j=10;
//错误!只读变量的值不能改变
(2)volatile关键字
volatile关键字和const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
例:
int i=10;
int j = i;//(1)语句
int k = i;//(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。这是一种内存被“覆盖”的情况。**编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。**但要注意:(1)、(2)语句之间 i 没有被用作左值才行。
再看另一个例子:
volatile int i=10;
int j = i; //(3)语句
int k = i; //(4)语句
volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在k中。这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
所以,volatile可以忽略编译器优化,保持内存可见性。
(3)sizeof关键字
siezof的主要作用就是计算数据类型长度。
各个类型的字节数:
int main()
{
printf("char %d\n", sizeof(char)); //1
printf("short %d\n", sizeof(short)); //2
printf("int %d\n", sizeof(int)); //4
printf("long %d\n", sizeof(long)); //4
printf("long long %d\n", sizeof(long long)); //8
printf("float %d\n", sizeof(float)); //4
printf("double %d\n", sizeof(double)); //8
return 0;
}
我们需要用到自定义的函数的时候,就得调用它,那么在调用的时候就称之为函数调用。
在C语言中,函数调用的一般形式为:
函数名([参数]);
[]中可以是常数,变量或其它构造类型数据及表达式,多个参数之间用逗号分隔。
在函数中不需要函数参数的称之为无参函数,在函数中需要函数参数的称之为有参函数。
有参和无参函数的一般形式如下:
函数的参数分为形参和实参两种。
形参
形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
形参特点:
形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。
形式参数有自己的内存空间,当函数被调用时才申请了该空间,才有了这个变量同时被赋值为实际参数的值。当函数执行结束后,该空间被内存管理单元自动回收。及释放空间
实参
实参是在调用时传递该函数的参数
实参特点
实参可以是常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值等办法使实参获得确定值。
在参数传递时,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配的错误。
函数的返回值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。
函数的返回值要注意以下几点:
return 表达式 或者为: return (表达式);
递归就是一个函数在它的函数体内调用它自身。
执行递归函数将反复调用其自身,每调用一次就进入新的一层。
注意递归函数必须有结束条件
阶乘计算的应用:
#include
#include
int digui(int num){
int result;
//越界判断!
if(num >= 17){
printf("越界,超出计算机计算位数\n");
exit(-1);
}
if(num == 0){
return 1;
}else{
return num*digui(num-1);
}
}
int main(int argc, char const *argv[])
{
int num;
int result;
printf("需要计算的阶乘:\n");
scanf("%d",&num);
result = digui(num);
printf("%d 的阶乘为 %d\n",num,result);
return 0;
}
程序中也需要容器,只不过该容器有点特殊,它在程序中是一块连续的,大小固定并且里面的数据类型一致的内存空间,它还有个好听的名字叫数组。
数据类型 数组名称[长度];
int arr[10];
数据类型 数组名称[长度n] = {元素1,元素2…元素n};
int arr[3] = {0,1,2};
数据类型 数组名称[] = {元素1,元素2…元素n};
int arr[] = {1,2,3};
数据类型 数组名称[长度n]; 数组名称[0] = 元素1; 数组名称[1] = 元素2; 数组名称[n-1] = 元素n;
int arr[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
数组名称[元素所对应下标];
arr[0];//即获得arr中第一个元素
int length = sizeof(arr)/sizeof(arr[0]);
数组可以由整个数组当作函数的参数,也可以由数组中的某个元素当作函数的参数:
#include
void printArr(int arr[],int len)
{
int i;
for (int i = 0; i < len; i++)
{
printf("%d ",arr[i]);
}
}
int main(int argc, char const *argv[])
{
int len;
int arr[4] = {1,2,3,8};
len = sizeof(arr)/sizeof(arr[0]);
printArr(arr,len);
return 0;
}
注意 :形参中不存在数组的概念,即便中括号约定了数组的大小也无效。
传递的是一个地址,是数组的首地址
void printArr(int a)
{
printf("%d ",a);
}
int main(int argc, char const *argv[])
{
int len;
int arr[4] = {1,2,3,8};
printArr(arr[0]);
return 0;
}
(1)冒泡法排序
以升序排序为例冒泡排序的思想:相邻元素两两比较,将较大的数字放在后面,直到将所有数字全部排序。
#include
#include
int main(int argc, char const *argv[])
{
int arr [] = {8,12,13,9};
int i ,j;
int size;
int temp;
size = sizeof(arr)/sizeof(arr[0]);
printf("size = %d\n",size );
printf("\n");
for (int i = 0; i < size - 1; i++)
{
for (int j = 0; j < size - i-1; j++)
{
if(arr[j]>arr[j+1]){
temp = arr[j+1];
arr[j+1]=arr[j];
arr[j] = temp;
}
}
}
printf("从小到大排序:\n");
for (int i = 0; i < size; ++i)
{
printf("%d ",arr[i]);
}
return 0;
}
(2)选择法排序
以升序排序为例的选择法排序,将第一个元素与之后的元素比较,确定第一个元素为最小的元素,之后在确定第二个、第三个以此类推,进行排序。
#include
#include
int main(int argc, char const *argv[])
{
int arr [] = {8,12,13,9};
int i ,j;
int size;
int temp;
size = sizeof(arr)/sizeof(arr[0]);
printf("size = %d\n",size );
printf("\n");
for (i = 0; i < size - 1; i++)
{
for (j = i+1; j < size; j++)
{
if(arr[i] > arr[j]){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
printf("从小到大排序:\n");
for (int i = 0; i < size; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
多维数组的定义格式是:
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n];
int arr[2][3] = {{1,2,3},{3,2,1}};
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n] = {{值1,…,值n},{值1,…,值n},…,{值1,…,值n}};
int arr[2][3] = {{1,2,3},{3,2,1}};
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n]; 数组名称[下标1][下标2]…[下标n] = 值;
int arr[2][3];
arr[0][0] = 1;
arr[0][1] = 2;
.....
arr[1][2] = 3;
采用第一种始化时数组声明必须指定列的维数。因为系统会根据数组中元素的总个数来分配空间,当知道元素总个数以及列的维数后,会直接计算出行的维数;
采用第二种初始化时数组声明必须同时指定行和列的维数。
#include
void printArr(int arr[][3])
{
int i;
int j;
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ",arr[i][j]);
}
putchar('\n');
}
}
int main(int argc, char const *argv[])
{
int i,j;
int arr[2][3] = {{1,2,3},{3,2,1}};
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
putchar('\n');
}
printArr(arr);
return 0;
}
指针在32位机器下是4个字节,在64位机器下是8个字节。注:(指针的大小与类型无关)
同理,对于(指针±整数)类的题也是如上的原理。
#include
#include
int main()
{ int a[2] = {10,1};
int* p1;//定义指针变量
p1=a;//取a的地址赋值给指针变量p
char c[2] = {'a','c'};
char *p2;
p2 =c;
printf("a的地址是%p a的值为%d\n",p1,*p1);//*p表示取指针变量存放的值及地址中存放的值。
printf("c的地址是%p a的值为%c\n",p2,*p2);//*p表示取指针变量存放的值及地址中存放的值。
p1++;
p2++;
printf("a+1的地址是%p a+1的值为%d\n",p1,*p1);//*p表示取指针变量存放的值及地址中存放的值。
printf("c+1的地址是%p c+1的值为%c\n",p2,*p2);//*p表示取指针变量存放的值及地址中存放的值。
system("pause");
return 0;
}
当一个int* 的指针+1,跳过4个字节;当一个char 的指针+1,跳过1个字节。
各种类型的大小:int类型四个字节,char类型一个字节,long,double,long long类型均为八个字节,long double类型为十六个字节
2.1概念:野指针顾名思义,指针指向的位置不可知。
2.2野指针产生的原因:
int * p;//(p就是一个野指针,及没有初始化的指针变量就是野指针)
指针越界访问
指针指向的空间释放
int* test( )
{
int a = 5;
return &a;
}
int main()
{
int* p = test();
*p = 10;
return 0;
}
变量a的地址只在test()函数内有效,当把a的地址传给指针p时,因为出了test函数,变量a的空间地址释放,导致p变成了野指针。
2.3 野指针的规避
1、数组元素的指针就是数组元素的地址(数组元素都在内存中占用存储单元,它们都有相应的地址)
2、数组名代表数组的首元素,引用其的方法为:
(1)下标法,如a[i]形式;
(2)指针法,如*(a+i)或*(p+i).
3、指针运算
(1)指针以指向一个数组元素时,可以进行以下运算
p+1,p-1,++p,p++,p- -,- -p
(p+1是指向同一数组中的下一个元素,加一代表的是加上一个数组元素所占用的字节数,同理可知p-1)
4、*(p+i) 或 * (a+i)
是p+i 或a+i所指向的数组元素,即a[i],三者等价([ ]是变址运算符,将a[i]按a+i计算地址,然后找出此地址单元的值
#include
int main()
{
int a[5]={1,2,3,4,5};
int *p;
p=a;
printf("%d\n",*(p+1));
printf("%d\n",*(a+1));
printf("%d",a[1]);
return 0;
}
5、如果两个指针指向同一个数组,它们就可以相减,其结果为两个指针之间的元素数目。
p2 - p1,指的是元素的相对距离 如下例:
计算的结果为4,即a[0]与a[4]之间的距离为4
#include
int main()
{
int a[5]={1,2,3,4,5};
int *p1,*p2;
p1 = &a[0];
p2 = &a[4];
printf("p2-p1= %d\n",p2-p1);
return 0;
}
int arr[3] = {1,2,3};
int *p = arr;
for(int i = 0;i<3;i++){
printf("%d\n",*p++ );
}
for(int i = 0;i<3;i++){
printf("%d\n",*arr++);//编译会报错,这里arr 是指针常量
}
对于一个二维数组
int a[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
#include
int main(int argc, char const *argv[])
{
int arr[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
printf("arr 的地址为%p ,arr+1的地址为%p\n",arr,arr+1); //arr+1 与arr 的地址相比较 偏移了16个字节
printf("arr[0]是子数组的地址为 %p, arr[0]+1 的地址为%p\n",arr[0],arr[0]+1); //arr[0]+1 与arr[0]相比 偏移了4个字节
printf("*arr 相当于 arr[0] 地址为 %p, 偏移1后的地址为%p\n", *(arr+0),*(arr+0)+1);//*(a+0) 等于a[0] *(a+1) 等于a[1]
return 0;
}
#include
int main(int argc, char const *argv[])
{
int arr[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
int i;
int j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("address:0x%p,data:%d\n",&(arr[i][j]),arr[i][j]);
printf("address:0x%p,data:%d\n",arr[i]+j,*(arr[i]+j));
printf("address:0x%p,data:%d\n",*(arr+i)+j,*(*(arr+i)+j));
printf("===================================================\n");
}
}
return 0;
}
int (*p)[3];
数组指针强调的的类型,数组的个数,偏移值是整个数组的大小!int main()
{
int ihang,ilie,data;
int arr[3][4] = {{1,3,2,5},{4,9,6,2},{0,6,5,1}};
int (*p) [4];//定义数组指针,指向数组的指针,这里指针偏移为 4*4 = 16个字节
p = arr;
printf("p = %p\n", p);
printf("++p = %p\n", ++p);
return 0;
}
int getData(int(*p)[4],int hang,int lie)//数组指针 每次偏移的列需要定义出来
{
return *(*(p+hang) +lie);
}
int main()
{
int ihang,ilie,data;
int arr[3][4] = {{1,3,2,5},{4,9,6,2},{0,6,5,1}};
//输入需要查找的行列:
printf("请输入要查找的行号和列号:");
scanf("%d%d",&ihang,&ilie);
data = getData(arr,ihang,ilie);
printf("%d行%d列的元素为%d",ihang,ilie,data);
system("pause");
return 0;
}
int* p[3];
//定义指针数组。多用指针数组存放函数指针。*
优先级高,因此p先与[3]结合,形成p[3]形式,这些显然是数组的形式,表示p数组又3个元素,然后再与p前面的*
结合,表示次数组是指针类型的,每个数组元素都可指向一个整型变量int main()
{
int a = 0;
int b = 1;
int* p1 = &a;
int* p2 = &b;
int* arr[] = { p1,p2 };//指针数组
int* arr[] = { &a,&b };//指针数组
return 0;
}
int arr[5] //名为arr的数组,数组里有5个元素,每个元素是int
int* p1[5] //指针数组,数组里有5个元素,每个元素是int*
int (*p2)[5] //数组指针,一个指向数组(里面有五个元素,每个元素是int)的指针
int (*p3[5])[5] //p3[5]是一个有5个元素的数组,数组里每个元素是int(*)[5]
int (*pf) (int,int),
pf函数返回的类型是intvoid print()
{
printf("1234\n");
}
int main()
{
void (*p)();
p=print;
print();
//通过函数指针访问函数方法1,直接用函数指针名字加()
p();
//方法2,用*去函数指针所指的函数内容
(*p)();
system("pause");
return 0;
}
#include
#include
int test(int num)
{
return ++num;
}
int main()
{
int (*ptest) (int nun); //返回值类型为int,且带有int类型的参数
ptest = test;
printf("%d\n", (*ptest)(10));
return 0;
}
#include
#include
int getMax(int data1,int data2)
{
return data1 > data2 ? data1:data2;
}
int getMin(int data1,int data2)
{
return data1 < data2 ? data1:data2;
}
int getAdd(int data1,int data2)
{
return data1+data2;
}
int funHandler(int data1,int data2,int (*pfun)(int ,int))
{
int ret;
ret = (*pfun)(data1,data2);
return ret;
}
int main()
{
int cmd;
int data1,data2;
int ret;
int (*pfun)(int ,int);
printf("请输入要运算的数组:\n");
scanf("%d %d",&data1,&data2);
printf("请输入要执行的操作(1:求最大值,2:求最小值,3:求和):\n");
scanf("%d",&cmd);
switch(cmd){
case 1: pfun = getMax;
break;
case 2: pfun = getMin;
break;
case 3: pfun = getAdd;
break;
default:
printf("输入错误!\n");
exit(-1);
}
ret= funHandler(data1,data2,pfun);
printf("ret = %d\n",ret);
return 0;
}
(2)结合指针数组对上例改写,将输入的数据进行求最大值,最小值,求和运算
#include
#include
int getMax(int data1,int data2)
{
return data1 > data2 ? data1:data2;
}
int getMin(int data1,int data2)
{
return data1 < data2 ? data1:data2;
}
int getAdd(int data1,int data2)
{
return data1+data2;
}
int main()
{
int cmd;
int data1,data2;
int ret;
int (*pfun[3])(int ,int) = {getMax,getMin,getAdd};//定义函数指针数组,并进行初始化
printf("请输入要运算的数字:\n");
scanf("%d %d",&data1,&data2);
printf("输入数字的最大值,最小值,和分别为:\n");
for (int i = 0; i < 3; i++)
{
ret = (*pfun[i])(data1,data2);
printf("%d\n",ret);
}
return 0;
}
int * a(int x,int y);
*a
两侧没有括号,在a的两侧分别为运算符和()运算符。而()优先级高于,因此a先与()结合,显然是函数形式。且函数前面有一个*,表示此函数时指针型函数。#include
int *getScore(int pos,int (*pstu)[4])
{
int *p;
/*将二维数组指针(偏移16字节) 转换为 int *类型的指针(偏移4字节)
从而找到对应行地址,并将行地址转换为一维数组地址*/
p = (int *)(pstu + pos);
return p;
}
int main(int argc, char const *argv[])
{
int scores[3][4] = { {90,91,92,99},
{89,88,89,100},
{87,85,78,100}
};
int pos;
int *ppos;
printf("请输入要查看学生的序号(0,1,2):\n");
scanf("%d",&pos);
ppos = getScore(pos,scores);
for (int i = 0; i < 4; i++)
{
printf("%d ",*ppos++);
}
return 0;
}
(2)对上例中的学生,找出其中有不及格的课程的学生及其学生号。
#include
int *getScore(int pos,int (*pstu)[4])
{
int *p;
/*//将二维数组指针(偏移16字节) 转换为 int *类型的指针(偏移4字节)
从而找到对应行地址,并将行地址转换为一维数组地址*/
p = (int *)(pstu + pos);
return p;
}
int *searchFail(int (*pstu)[4])
{
int *p;
for (int i = 0; i < 4; i++)
{
//printf("%d ",*(*(pstu+0)+i));
if(*(*pstu+i) < 60){
p = pstu;
}
}
return p;
}
int main(int argc, char const *argv[])
{
int scores[3][4] = { {90,91,92,99},
{89,59,89,100},
{87,85,78,100}
};
int pos;
int *ppos;
int *failPos;
printf("请输入要查看学生的序号(0,1,2):\n");
scanf("%d",&pos);
ppos = getScore(pos,scores);
for (int i = 0; i < 4; i++)
{
printf("%d ",*ppos++);
}
putchar('\n');
for (int i = 0; i < 3; i++)
{
failPos = searchFail(scores+i);
if(failPos == *(scores+i)){
printf("第%d个学生不及格\n",i+1);
for (int j = 0; j < 4; j++)
{
printf("%d ",scores[i][j]);
}
}
}
return 0;
}
#include
void getScore(int pos,int (*pstu)[4],int **ppos)//这里int **ppos为二级指针,接收主函数传来的指针的地址
{
*ppos = (int *)(pstu + pos); //这里修改主函数传入的指针的地址,并访问其保存的指针(通过*ppos)的方法,达到改变其指向的新地址
}
int main(int argc, char const *argv[])
{
int scores[3][4] = { {90,91,92,99},
{89,88,89,100},
{87,85,78,100}
};
int pos;
int *ppos;
printf("请输入要查看学生的序号(0,1,2):\n");
scanf("%d",&pos);
getScore(pos,scores,&ppos);//这里&ppos 传入的是指针ppos的地址 即二级指针
for (int i = 0; i < 4; i++)
{
printf("%d ",*ppos++);
}
return 0;
}
1.一个指向指针的指针,它指向的指针指向一个整型数 : int **a;
2.一个有10个整型数的数组 :int a[10];
3.一个有10个指针的数组,每个指针指向一个整型数: int * a[10];
4.一个指向有10个整型数的数组指针: int (*a)[10];
5.一个指向指针的指针,被指向的指针指向一个有10个整型数的数组:int (**a)[10];
6.一个指向数组的指针,该数组有10个整型指针: int *(*a)[10];
7.一个指向函数的指针,该函数有一个整型参数并返回一个整型数: int (*a)(int);
8.一个有10个指针的数组,每个指针指向一个函数,该函数有一个整型参数并返回一个整型数: int (*a[10])(int);
9.一个函数的指针,指向的函数类型是有两个整型参数并返回一个函数指针的函数,返回的函数指针指向有一个整型参数且返回整型数的函数:
指向有两个整型参数的函数指针: (*a)(int,int);
返回的函数指针 是有一个整型参数且返回整型的函数:int *() (int)
将定义的函数指针放到返回值类型(返回值类型为函数指针):int *((*a)(int,int))(int);
malloc(size-t);
函数:返回值为void*
(无类型指针) 参数中的size-t指的是开辟了这么多字节的空间。int *parry =(int*) malloc(n*sizeof(int));
int *parry =(int*) malloc(12);
相当于int parry[3]; int n;
printf("输入数组的个数%d",n);
scanf("%d",&n);
int a[n];
//这种方式不合法可以用malloc函数定义:
int*parray=(int*)malloc(n*sizeof(int));
int *p =malloc(1024)
int *p =malloc(1024);
if(p==NULL){
printf(“申请内存失败”);
exit(-1);//结束进程,-1代表出现异常。
}
free()
函数进行释放空间free(p);p=NULL;
(释放后注意p变为了野指针,应当给它初始化!)char是一种整数,也是一种特殊的类型——字符。用单引号表示的字符字面量:‘a’,'1’也是一个字符,printf和scanf里面用%c来输入输出字符。
1.scanf()函数
scanf("%d",&p);
%d说明我们现在要读入一个整数了,scanf这个函数会读入下一个整数,读到的结果赋值给指定变量,要注意指定变量前面的&
如果要读入输入的数据,就必须严格按照""中的格式输入,如:
int main()
{
int a,b;
scanf("%d,%d",&a,&b);
printf("%d %d\n",a,b );
return 0;
}
int main()
{
char word[8];
scanf("%s",word);
printf("%s\n",word);
return 0;
}
这说明读取完字符o后,并没有将后面的空格读取出来,遇到空格就直接停止读取了。
2.getchar和putchar函数用法
(1)getchar函数
函数格式: int getchar()
所在头文件:
函数功能: 从输入缓冲区中读取单个字符
函数返回值: 成功时为获得的字符,失败时为 EOF 。
(2)putchar函数
函数格式: int putchar(int ch)
所在头文件:
函数功能: 输出字符到标准输出上,等价于 putc(ch, stdout)
函数参数: ch,表示要写入的字符,可以是字符常量,转义字符,可以是介于0~ 127之间的一个十进制整型数(包含0和127,超过127就不是ASCII码了),当参数为一个介于0~127(包括0及127)之间的十进制整型数时,它会被视为一个ASCII代码,输出该ASCII代码对应的字符,也可以是一个字符变量。
参数为是一个字符,但参数却是int型,这表示1字节的字符存在4字节大小的内存中。
或者换个理解方式,字符对应的ASCII码值用int型参数来接收。
函数返回值:
成功时,返回参数的ASCII码值。
失败时,返回 EOF 并设置 stdout 上的错误指示器。
3.getchar和putchar函数工作原理:
getchar()用于从键盘输入中读取单个字符,当我们连续输入多个字符时,第一个getchar()读取第一个字符,后续的getchar()依次读取后续的字符,当getchar()读取到EOF或者读取到回车时停止执行。
while ((ch = getchar()) != EOF) {
putchar(ch);
}
#include
int main()
{
char ch = ' ';
while ((ch = getchar()) != EOF) {
putchar(ch);
}
return 0;
}
我们输入abc按下回车后,
输入缓冲区中的内容:abc\n
由于遇到\n,因此getchar开始从输入缓冲区中读取字符,先读取a,满足输出while循环条件,可以输出,先存储到输出缓冲区中,然后读取b,满足输出while循环条件,可以输出,先存储到输出缓冲区中,然后读取c,满足输出while循环条件,可以输出,先存储到输出缓冲区中,最后读取\n,满足输出while循环条件,可以输出,先存储到输出缓冲区中。
此时输出缓冲区中的内容:abc\n
由于输出缓冲区遇到了\n,所以进行一次系统调用,将输出缓冲区的内容直接输出到标准输出上。
1.自符串的定义:
char* str=” Zhang ”;
相当于char str []=”Zhang”;
这里直接使用指针因为数组的名称相当于首元素的地址。
char* str=” Zhang ”;
是字符串的常量,内容不可以修改。char str []=”Zhang”;
则为字符串变量,可以修改!printf(“%s”,str);
//调用时直接用%s占位符即可。strlen();在计算字符大小时遇到’\0’就停止了计数。
sizeof 计算字符数组大小时计算全部数组大小,即:
char data [128] = “hello”;
sizeof(data) = 128;
strlen(data) = 5;
3.注意出现野指针造成非法内存访问,出现段错误。
形如:
char*pstr;
scanf(“%s”,pstr);
puts(pstr);
便会造成非法内存访问。定义出来的pstr 成为了一个野指针,指针变量里面所存放的东西未知且系统没有给空间。
解决方法:
方法1:(直接用字符数组并在内存放128个’\0’)
`char pstr[128]=’\0’;`//申请空间并做了初始化,把每个元素都初始化为’\0’
scanf(“%s”,pstr);
puts(pstr);
方法2:(用malloc函数开辟空间)
char*pstr=(char*)malloc(128);
4.malloc 动态开辟内存空间
与malloc 相关的函数
(Linux下)
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void *malloc(size_t size);
分配所需的内存空间。并返回一个指向它的指针
void free(void *ptr);
释放,防止内存泄露 当内存不再使用的时候,应使用free()函数将内存块释放掉。
void *calloc(size_t nmemb, size_t size);
calloc(10 , sizeof(int)) calloc在返回在堆区申请的那块动态内存的起始地址之前,会将每个字节都初始化为0
void *realloc(void *ptr, size_t size);
尝试重新调整之前malloc或calloc所分配的指针所指向内存块的大小
5.字符串的操作函数:(调用函数时需要包含文件#include
(1)puts(str);
//输出字符串函数。(此函数可以自动加换行\n)
#include
#include
int main()
{
char * str = "hello";
puts(str);
return 0;
}
(2)gets(str);
#include
#include
#include
int main()
{
char *str;
str = (char *)malloc(32);
gets(str);
printf("str:%s\n",str);
return 0;
}
(3)void *memset(void *s, int c, unsigned long n);
第一个参数void* mem_loc
:已开辟内存空间的首地址,通常为数组名或指针,由于其为void*
,故函数能为任何类型的数据进行初始化。
第二个参数int c
:初始化使用的内容,取器低字节部分。
第三个参数size_t n
:需要初始化的字节数。
#include
#include
#include
int main()
{
char char_arr[5];
int int_arr[10];
int* int_malloc_ptr = (int*)malloc(sizeof(int) * 15);
memset(char_arr, 0, sizeof(char_arr)); // 数组名可以使用sizeof(arrName), 1 * 5 = 5
memset(int_arr, 0, sizeof(int_arr)); // 4 * 10 = 40
memset(int_malloc_ptr, 0, sizeof(int) * 15); // 4 * 15 = 60
printf("%s\n",char_arr);
return 0;
}
(4)strcpy(str1,str2);
字符串拷贝
把str2的内容拷贝到str1中
#include
#include
#include
int main()
{
char *str1 = "Hello World";
char *str2;
strcpy(str2,str1);
printf("str2:%s\n",str2);
return 0;
}
(5)strncpy(str1,str2,int n);
把str2的内容拷贝到str1中,拷贝n个字符
#include
#include
#include
int main()
{
char *str1 = "Hello World";
char *str2;
strncpy(str2,str1,6);
printf("str2:%s\n",str2);
return 0;
}
(6)strcat(str1,str2);
//拼接函数 把str1和str2接到一起。
注意
由于要改变字符串需要将字符串定义为数组形式
#include
#include
#include
int main()
{
char str1[] = "Hello ";
char str2[] = "World";
strcat(str1,str2);
printf("str1:%s\n",str1);
return 0;
}
(7)strlwr(str);
将一段字符转为小写
注意定义str时只能用数组定义
#include
#include
#include
int main()
{
char str1[] = "HELLO";
strlwr(str1);
printf("str1:%s\n",str1);
return 0;
}
(8)strupr(str);
将一段字符转为大写。
#include
#include
#include
int main()
{
char str1[] = "hello";
strupr(str1);
printf("str1:%s\n",str1);
return 0;
}
(9)strtok(str1,”,”);
字符切割 将str1以,为结束标志切割
注意只能分割出来第一个字符串若想继续分割则要继续调用strok()调用而且要把目标字符串改成NULL strok(NULL,”,”);
#include
#include
#include
int main()
{
char str1[]="hello,world";
char*p=NULL;
p=strtok(str1,",");
puts(p);//注意只能分割出来第一个字符串若想继续分割则要继续调用strok()调用而且要把目标字符串改成NULL strok(NULL,”,”);
return 0;
}
#include
#include
#include
int main()
{
char str[]="hello,world";
char*p=NULL;
p=strtok(str,",");
puts(p);
p=strtok(NULL,",");
puts(p);
return 0;
}
(10)strchr();
检索字符在一个字符串中出现的位置
#include
#include
#include
int main()
{
char*str="hello world";
char c='w';
char*p=NULL;
//str 要被检索的字符串,c在str中要搜索的字符,返回值 返回字符串中第一次出现该字符的指针。若字符串中不包含该字符则返回NULL。
p=strchr(str,c);
puts(p);
system("pause");
return 0;
}
可见得到了第一次出现的’w’指针,并返回"world"字符串
(11)strstr();
检索子串在一个字符串中出现的位置,并返回子串指针。
#include
#include
#include
int main()
{
char*str="hello world!";
char*p=NULL;
char*substr="wor";
p=strstr(str,substr);
if(p==NULL)
{
printf("没有找到\n");
}
else{
puts(p);
}
return 0;
}
(12)int ret =strcmp(char *s1,char*s2);
(1). 如果两个字符串相等,则返回0;
#include
#include
#include
int main()
{
char* str1 = "helloworld";
char* str2 = "helloworld";
int ret;
ret = strcmp(str1,str2);
printf("%d\n",ret);
return 0;
}
(2). 若str1大于str2,返回一个正数,这个正数不一定是1;
#include
#include
#include
int main()
{
char* str1 = "helloworld";
char* str2 = "hellow!";
int ret;
ret = strcmp(str1,str2);
printf("%d\n",ret);
return 0;
}
(3). 若str1小于str2,返回一个负数,这个数不一定是-1
#include
#include
#include
int main()
{
char* str1 = "helloworld!";
char* str2 = "hello!";
int ret;
ret = strcmp(str2,str1);
printf("%d\n",ret);
return 0;
}
#include
#include
#include
int main()
{
char* str1 = "hello world";
char* str2 = "helloworld";
int ret;
ret = strcmp(str1,str2);
printf("%d\n",ret);
return 0;
}
(13)断言函数:
#include
assert的作用是现计算表达式expression,如果其值为假(0)则会打印错误消息终止运行:
6.自己实现的字符串操作函数
(1)strcpy
#include
#include
#include
char* myStrcpy(char *des_str,char *src_str)
{
assert(des_str != NULL&&src_str != NULL);
char *temp = des_str;
while(*src_str != '\0'){
*des_str++ = *src_str++;
}
*des_str = '\0';
return temp;
}
int main()
{
char * str = "hello world!";
char str1[128] = {'\0'};
myStrcpy(str1,str);
printf("%s\n",str1);
system("pause");
return 0;
}
(2)strcat
char *myStrcat(char *des_str,char *src_str)
{
assert(des_str!=NULL&&src_str!=NULL);
char *temp = des_str;
while(*des_str != '\0'){
des_str++;
}
while((*des_str++ = *src_str++)!='\0');
*des_str = '\0';
return temp;
}
int main()
{
char *str = "world!";
char str1[32] = "hello ";
char *p2;
p2 = myStrcat(str1,str);
printf("%s\n",p2);
system("pause");
return 0;
}
(3)strcmp
int myStrcmp(char *str1,char *str2)
{
int str1Len;
int str2Len;
while(*str1!='\0'){
str1++;
str1Len++;
}
while(*str2!='\0'){
str2++;
str2Len++;
}
if(str1Len < str2Len){
return -1;
}else if (str1Len == str2Len)
{
return 0;
}else if(str1Len > str2Len){
return 1;
}
}
在实际问题中,一组数据往往具有不同的数据类型;例如在学生信息登记表中,姓名为字符型,学号为整型或字符型,年龄为整型,性别为字符型,成绩为整型或实型。因为数据类型不同,显然不能用一个数组来存放。
在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate datatype)的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。
struct student
{
int score;
char name[128];
};
#include
#include
#include
struct student
{
int score;
char name[128];
};
int main()
{
struct student stu1={98,"zhangSan"};
printf("name:%s score=%d\n",stu1.name,stu1.score);
return 0;
}
#include
#include
#include
#include
struct student
{
int score;
char name[128];
};
int main()
{
struct student stu1={98,"zhangSan"};
struct student test;
test.score=100;
strcpy(test.name,"张三");//注意在C语音中不能用test.name="张三";这种方法!
printf("name:%s score=%d\n",test.name,test.score);
return 0;
}
struct DATA{
char*p;
};
应该以这种方式定义。
struct DATA{
char p[128];
};
如果以第一种方法定义的话 运用时要注意初始化!
struct DATA d;
d.p=(char*) malloc(128);//申请空间
memset(d.p,’\0’,128);//初始化
#include
#include
#include
#include
struct student
{
int score;
char name[128];
};
int main()
{
struct student *p;
struct student stu1;
stu1.score=100;
strcpy(stu1.name,"huangSi");
printf("name:%s score=%d\n",test.name,test.score);
p=(struct student*)malloc(sizeof(struct student));//要给指针p开辟空间!!
p->score=98;
strcpy(p->name,"zhangSan");
printf("%s的分数为%d\n",p->name,p->score);
free(p);//指针是存放地址的变量,之前指向的malloc开辟的地址,之后指的是stu1的地址。
//因此为了防止内存泄漏,先把p原先指向的malloc所开辟的空间释放。
p=&stu1;
printf("%s的分数为%d\n",p->name,p->score);
return 0;
}
定义结构体指针 struct student *p;
不过是一个野指针。
如果定义结构体指针,就不能用.运算符访问结构体里的内容,应该用->来访问
在上例中:
指针p是存放地址的变量,之前指向的malloc开辟的地址,之后指的是stu1的地址。
因此为了防止内存泄漏,先把p原先指向的malloc所开辟的空间释放。 free(p);
再将p指向stu1. p=&stu1;
计算法则
1.结构体成员的偏移量,是其成员的大小的整数倍(0是被认为是所有数的整数倍)
2.结构体的大小必须是所有成员大小的整数倍
3.结构体中数组,结构体只计算其大小,不参与1、2的法则判定,
1.结构体成员的偏移量,是其成员的大小的整数倍(0是被认为是所有数的整数倍)
struct T1{ //偏移量 大小
char a; //0 1
char b; //1 1
int c; //2 4 (空出2个字节)
//结构体大小: 1+1+2+4=8
};
对应char a;偏移量大小为0,本身大小为1,
对于char b;偏移量大小为1,本身大小为1,均符合条件
对于int c;偏移量大小为2,本身大小为4,故需要在多偏移2字节
最终结构体大小为8字节
2.结构体的大小必须是所有成员大小的整数倍
struct s2{ //偏移量大小
char ch1;//0 1
int i; //1 4
char ch2;//8 1
};
对于ch1;偏移量大小为0,本身大小为1,
对于i ; 偏移量大小为1,本身大小为4,不满足第一个条件,需要再多偏移3,
对于ch2;偏移量为8,本身大小为1
此时结构体大小为1+4+3+1 = 9,不满足结构体的大小必须是所有成员大小的整数倍条件,故还需补充到12个字节满足条件
3.结构体中数组,结构体只计算其大小,不参与1、2法则判定
(1)结构体中包含数组
struct T1{ //偏移量 大小
char a; //0 1
int b; //1 4 (空出3个字节)
char c[13]; //8 13 (数组不包含在法则内,还是13个字节无空出字节)
//结构体大小: 1+4+3+13=21, 根据法则,21不是4的整数倍,所以为24
};
前两个变量还是一样的计算,字符不用考虑法则1,2,所以直接加上数组的大小,同时不考虑数组的偏移量与大小的关系
(2)结构体中包含结构体
结构体内的结构体定义与否会影响整个和结构体大小,但任然不参与法则判定
①.对于解结构内的结构体,如果内部结构体声明了结构体却没定义,那么其大小不计算在整个结构体的大小内
struct T1{ //偏移量 大小
char a; //0 1
int b; //1 4 (空出3个字节)
struct T2{ //声明无大小
char c;
int d;
};
float e; //8 4
//1+4+3+4 = 12
};
如果不定义结构体T2变量,直接打印sizeof(struct T1),在Linux下会警告
②.前面a,b同理,结构体类型的成员变量继续偏移算结构体总大小,此时T2声明并定义了变量temp,易得T2结构体大小为8,e偏移了16字节,大小为4没有多余空出字节,所以大小为1+4+3+8(结构体)+4=20;
struct T1{ //偏移量 大小
char a; //0 1
int b; //1 4 (空出3个字节)
struct T2{ //整各大小为8
char c; //0 1
int d; //1 4 (空出3个字节)
}temp;
float e; //16 4
//1+4+3+1+4+3+4 = 20
};
1.数据对齐
现在计算机内存空间都是按照byte划分的,理论上来说任何变量从任何地址开始都可以。但是为了配合硬件,就需要进行数据对齐,将各个类型的数据按照一定的规则在空间进行排布,而不是顺序的一个个排放。
当我们定义一个变量时,编译器会按默认的地址对齐来一块存储地址空间。比如定义了一个int型的变量,那么改变量会4字节或4字节的整数倍对齐,就是存放在地址能整除4的地址空间内(PS:0x0004这样,能整除4的)。
2.数据对齐规则
这里引入三个概念:自身对齐值,指定对齐值,有效对齐值。
1)自身对齐值:数据类型的自身对齐值,如char的自身对齐值是1,int是4;
2)指定对齐值:编译器或者程序员指定的(#pragma pack (value))
默认对齐值。
PS:结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3)有效对齐值:自身对齐值和指定对齐值中较小的那个值。
对齐有两个规则:
1、不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值;
2、存放成员的起始地址必须是该成员有效对齐值的整数倍。
例:
指定对齐:
#pragma() pack(4)//指定向4对齐,最大是8,没有超过最大的double c 8。
struct s1{
char a;
int b;
float c;
double d;
};//这种情况按照4对齐所以此结构体的大小是20
假如结构体起始地址是0x0000,
成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节;
成员b的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0004是4的整数倍,故b存放起始地址是0x0004,占四个个字节(0x0004-0x0007);
成员c的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0008是4的整数倍,故c存放起始地址是0x0008,占四个字节(0x0008-0x000B);
成员d的自身对齐值8,指定对齐值4,所以有效对齐值是4,地址0x000C是4的整数倍,故d存放起始地址是0x000C,占8个字节(0x000C-0x0014);
此时结构体A的有效对齐值是其指定对齐值,为4,故结构体s1的有效对齐值是4,下载A占字节数为0x0000-0x0014,20个字节是4的整数倍,所以不需要额外补齐了。最后结构体s1占总字节数为20。
ps:0x0001、0x0002、0x0003内存空间都空着
#pragma() pack(10)//指定向10对齐,最大是8,超过最大的double c 8。
sstruct s2{
char a;
int b;
float c;
double d;
};//这种情况下还按照8对齐所以结构体大小是24。
假如结构体起始地址是0x0000,
成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节;
成员b的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0004是4的整数倍,故b存8放起始地址是0x0004,占四个个字节(0x0004-0x0007);
成员c的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0008是4的整数倍,故c存放起始地址是0x0008,占四个字节(0x0008-0x000B);
成员d的自身对齐值8,指定对齐值8,所以有效对齐值是8,地址0x00010是8的整数倍,故d存放起始地址是0x0010,占8个字节(0x0010-0x0017);
此时结构体A的有效对齐值是其指定对齐值,为8,故结构体s2的有效对齐值是8,下载A占字节数为0x0000-0x0017,24个字节是8的整数倍,所以不需要额外补齐了。最后结构体s2占总字节数为24。
ps:0x0001、0x0002、0x0003、0x000C、0x000D、0x000E内存空间都空着
在C语言中,变量的定义是分配存储空间的过程。一般的,每个变量都具有其独有的存储空间,那么可不可以在同一个内存空间中存储不同的数据类型(不是同事存储)呢?
答案是可以的,使用联合体就可以达到这样的目的。联合体也叫共用体,在C语言中定义联合体的关键字是union。
union 联合名
{
成员表
};
成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名。其占用的字节数与成员中最大数据类型占用的字节数。
// 创建联合体模板union perdata
union perdata
{
int Class;
char Office;
};
// 使用该联合体模板创建两个变量a, b
union perdata a,b;
方法二:同时创建模板和变量
// 创建联合体模板union perdata的同时定义两个变量a、b
union perdata
{
int Class;
char Office;
}a,b;
方法三:省略联合体名
union
{
int Class;
char Office;
}a,b;
方法四:使用typedef
// 联合体模板union perdata重新命名为perdata_U
typedef union perdata
{
int Class;
char Office;
}perdata_U;
// 使用新名字perdata_U创建两个变量a, b
perdata_U a,b;
(2)联合体初始化
联合体的初始化与结构体不同,联合体只能存储一个值。
perdata_U a;
a.Class = 10;
perdata_U b = a; /* 1、把一个联合初始化为另一个同类型的联合; */
perdata_U c = {20}; /* 2、初始化联合的第一个成员; */
perdata_U d = {.Office = 30}; /* 3、根据C99标准,使用指定初始化器。 */
根据输入人的职位存储不同的信息,当输入为教师时,用char 类型存储其教授的科目,当输入为学生时,用int 类型存储其所在班级:
#include
struct Person
{
char name[32];
int age;
char addr[12];
char position;
union {
int class;
char subject[12];
} pesonMes;
};
int main(int argc, char const *argv[])
{
struct Person p [2];
int i;
for (i = 0; i < 2; i++)
{
printf("请输入人员的职位(s:学生,t:教师):\n");
scanf("%c",&(p[i].position));
if(p[i].position == 's'){
printf("请输入学生的名字:\n");
scanf("%s",&(p[i].name));
printf("请输入学生的班级:\n");
scanf("%d",&(p[i].pesonMes.class));
}
else if(p[i].position == 't'){
printf("请输入教师的名字:\n");
scanf("%s",&(p[i].name));
printf("请输入教师所教的科目:\n");
scanf("%s",&(p[i].pesonMes.subject));
}else {
printf("err!\n");
break;
}
getchar();
}
for (int i = 0; i < 2; i++)
{
if(p[i].position == 's'){
printf("学生姓名:%s,学生班级:%d\n",p[i].name,p[i].pesonMes.class);
}else if(p[i].position == 't'){
printf("教师姓名:%s,教师班级:%s\n",p[i].name,p[i].pesonMes.subject);
}
}
return 0;
}
1.概念 :枚举是C语言中的一种基本数据类型,它可以让数据更简洁,更易读。
enum 枚举名
{
枚举元素1,枚举元素2,…… //注意,各元素之间用逗号隔开
}; //注意,末尾有分号
编译器自动分配的原则是:从0开始依次增加。如果用户自己定义了一个值,则从那个值开始往后依次增加。
C语言为何需要枚举?
C语言没有枚举是可以的。使用枚举其实就是对一些数字进行符号化编码,这样的好处就是编程时可以不用看数字而直接看符号。符号的意义是显然的,一眼可以看出。而数字所代表的含义除非看文档或者注释。
这么看来,宏定义也能实现呀!要专门弄个枚举类型干嘛?
宏定义和枚举有内在联系,宏定义和枚举经常用来解决类似的问题,他们俩基本相当可以互换,但是有一些细微差别。
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
枚举示例:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
枚举类型需要先定义后使用,这里的定义是类型的定义,不是枚举变量的定义。
既然枚举也是一种数据类型,那么它和基本数据类型一样也可以对变量进行声明。
(1)枚举类型的定义和变量声明分开
//先定义类型
enum DAY //类型名称就是enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
//后声明变量
enum DAY yesterday;
enum DAY today;
enum DAY tomorrow; //变量tomorrow的类型为枚举型enum DAY
enum DAY good_day, bad_day; //变量good_day和bad_day的类型均为枚举型enum DAY
(2)类型定义和变量声明同时进行
enum //跟第一个定义不同的是,此处的标号DAY省略,这是允许的。
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday; //变量workday的类型为枚举型enum DAY
(3)用typedef关键字将枚举类型定义成别名,并利用该别名进行变量声明
typedef enum workday
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday; //此处的workday为枚举型enum workday的别名
//或者
//enum workday中的workday可以省略
typedef enum
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday; //此处的workday为枚举型enum workday的别名
//定义变量
//变量today和tomorrow的类型为枚举型workday,也即enum workday
workday today, tomorrow;
(1)先声明后赋值:
#include
/* 定义枚举类型 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
/* 使用基本数据类型声明变量,然后对变量赋值 */
int x, y, z;
x = 10;
y = 20;
z = 30;
/* 使用枚举类型声明变量,再对枚举型变量赋值 */
enum DAY yesterday, today, tomorrow;
yesterday = MON;
today = TUE;
tomorrow = WED;
printf("%d %d %d \n", yesterday, today, tomorrow);
}
就像各种类型都有取值范围一样,枚举变量只能接收枚举类型中定义好的符号值(实质是一个int类型的数据)
(2)声明的同时赋值
#include
/* 定义枚举类型 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
/* 使用枚举类型声明变量,再对枚举型变量赋值 */
enum DAY yesterday = MON,
today = TUE,
tomorrow = WED ;
printf("%d %d %d \n", yesterday, today, tomorrow);
}
(1)typedef创建的符号名只限于类型,不限于值
(2)typedef由编译器解释,不是预处理器
1)typedef基本数据类型取“别名”
也就是说,C语言中的所有数据类型都可以用typedef关键词来重新定义类型名
typedef unsigned int size;
typedef unsigned int16 u16;
typedef unsigned int8 u8;
2)typedef为自定义数据类型取“别名”
自定义的数据类型包括:结构体struct name{ }; 、共用体unit name { };、枚举enum { };
struct students
{
char sex;
char name[120];
int ages;
};
typedef struct students std;
int main()
{
std stu1;
stu1.name[20];
strcpy(stu1.name,"helwww");
return 0;
}
3)typedef为数组取“别名”
#include
#include
#include
#include
typedef char arr_name[20];
int main()
{
arr_name ane;
strcpy(ane,"helllo");
printf("%s\n",ane);
return 0;
}
4)typedef为指针取“别名”
typedef int (*PF)(int,int);//函数指针别名
PF a=NULL;
typedef int (*PFS[4])(int,int);//函数指针别名
PFS b={NULL};//定义了一个函数指针数组,4个长度
例程:
#include
#include
#include
#include
typedef int (*PF)(int,int);//函数指针别名
typedef int (*PFS[4])(int, int);//函数指针数组别名
int add(int a, int b) {//两个数相加
return a + b;
}
int subtract(int a, int b) {//两个数相减
return a - b;
}
int multiply(int a, int b) {//两个数相乘
return a * b;
}
int divide(int a, int b) {//两个数相除
return a / b;//会丢失精度
}
//返回函数指针
PF judge(char buff) {//判断运算符进而选择适合的函数
switch (buff)
{
case '+':return add;
case '-':return subtract;
case '*':return multiply;
case '/':return divide;
default :return NULL;
break;
}
}
int main()
{
char c = 0;
int a = 0;
int b = 0;
int (*function)(int, int)=NULL;//函数指针
int x;
printf("请输入表达式:\n");
x=scanf("%d%c%d", &a,&c,&b);
function = judge(c);
if (function != NULL) { //判断指针是否合法
printf("表达式%d %c %d = %d\n", a, c, b, function(a, b));
}else printf("表达式输入错误\n");
}
typedef char* PCHAR;
int strcmp(const PCHAR,const PCHAR);
在上面的代码中,“const PCHAR” 是否相当于 “const char*” 呢?
答案是否定的,原因很简单,typedef 是用来定义一种类型的新别名的,它不同于宏,不是简单的字符串替换。因此,“const PCHAR”中的 const 给予了整个指针本身常量性,也就是形成了常量指针“char* const
(一个指向char的常量指针)”。即它实际上相当于“char* const”
,而不是“const char*
(指向常量 char 的指针)”。
struct link{
int data; //定义数据域
struct link *next; //定义指针域,存储直接后继的节点信息
};
数据域的内容可以自己指定,指针域用来存放下一个节点的地址。
**头节点:**在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点,头结点的数据域可以不存储任何信息,指针域指向第一个节点(首节点)的地址。头结点的作用是使所有链表(包括空表)的头指针非空
**头指针:**指向头节点的指针
**尾节点:**存放最后一个有效数据的节点
**尾指针:**指向尾节点的指针
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
int main()
{
struct Test *head=NULL;
struct Test t1={1,NULL};
struct Test t2={2,NULL};
struct Test t3={3,NULL};
struct Test t4={4,NULL};
head = &t1;
t1.next=&t2;
t2.next=&t3;
t3.next=&t4;
printlink(head);
system("pause");
return 0;
}
(1)头插法
struct Test *insertFromHead(struct Test*head,struct Test *new)
{
if(head == NULL){
head = new;
}else{
new->next=head;
head=new;
}
return head;
}
用头插法动态创建链表
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
struct Test *insertFromHead(struct Test*head,struct Test *new)
{
if(head == NULL){
head = new;
}else{
new->next=head;
head=new;
}
return head;
}
struct Test *createLink(struct Test *head)
{
struct Test *new;
while(1){
new=(struct Test *)malloc(sizeof(struct Test));
printf("输入数据(输入0结束输入)\n");
scanf("%d",&(new->a));
new->next = NULL;
if(new->a==0){
free(new);
return head;
}
head=insertFromHead(head,new);
}
}
int main()
{
struct Test *head=NULL;
head = createLink(head);
printlink(head);
system("pause");
return 0;
}
(2)尾插法
struct Test *insertFromTail(struct Test*head,struct Test *new)
{
struct Test *p=head;
if(p==NULL){
head=new;
return head;
}
while(p->next!=NULL){
p=p->next;
}
p->next=new;
return head;
}
用尾插法动态创建链表
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{
struct Test *p=head;
if(p==NULL){
head=new;
return head;
}
while(p->next!=NULL){
p=p->next;
}
p->next=new;
return head;
}
struct Test *createLink(struct Test *head)
{
struct Test *new;
while(1){
new=(struct Test *)malloc(sizeof(struct Test));
printf("输入数据(输入0结束输入)\n");
scanf("%d",&(new->a));
new->next = NULL;
if(new->a==0){
free(new);
return head;
}
head=insertFromTail(head,new);
}
}
int main()
{
struct Test *head=NULL;
head = createLink(head);
printlink(head);
system("pause");
return 0;
}
(1)增
int insertFro(struct Test *head,int data,struct Test *new)
{
struct Test *p;
p = head;
if(p->a == data){
new->next = head;
head = new;
return 1;
}
while(p->next!=NULL){
if(p->next->a == data){
new -> next = p->next;
p->next = new;
return 1;
}
p = p->next;
}
return 0 ;
}
int insertBehind(struct Test *head,int data,struct Test * new1)
{ struct Test*p=head;
while(p!=NULL){
if(p->a==data){
new1->next=p->next;
p->next=new1;
return 1; }
p=p->next;
}
return 0;
}
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{
struct Test *p=head;
if(p==NULL){
head=new;
return head;
}
while(p->next!=NULL){
p=p->next;
}
p->next=new;
return head;
}
struct Test *createLink(struct Test *head)
{
struct Test *new;
while(1){
new=(struct Test *)malloc(sizeof(struct Test));
printf("输入数据(输入0结束输入)\n");
scanf("%d",&(new->a));
new->next = NULL;
if(new->a==0){
free(new);
return head;
}
head=insertFromTail(head,new);
}
}
/*
在指定数据后节点
*/
int insertBehind(struct Test *head,int data,struct Test * new1)
{ struct Test*p=head;
while(p!=NULL){
if(p->a==data){
new1->next=p->next;
p->next=new1;
return 1; }
p=p->next;
}
return 0;
}
int insertFro(struct Test *head,int data,struct Test *new)
{
struct Test *p;
p = head;
if(p->a == data){
new->next = head;
head = new;
return 1;
}
while(p->next!=NULL){
if(p->next->a == data){
new -> next = p->next;
p->next = new;
return 1;
}
p = p->next;
}
return 0 ;
}
int main()
{
struct Test *head=NULL;
struct Test stu1 ;
int data;
int ret;
head = createLink(head);
printf("=================插入节点前=================\n");
printlink(head);
printf("请输入在哪个节点前插入节点:\n");
scanf("%d",&data);
printf("请输入需要插入的节点\n");
scanf("%d",&(stu1.a));
stu1.next = NULL;
ret = insertFro(head,data,&stu1);
if(ret){
printf("插入节点成功!\n");
}else{
printf("插入失败\n");
}
printf("=================插入节点后=================\n");
printlink(head);
system("pause");
return 0;
}
(2)删
删除指定的节点
int deleteNode(struct Test *head,int data)
{
struct Test *p,*ptemp;
p=head;
if (p->a == data)
{
ptemp = head;
head = head->next;
free(ptemp);
return 1;
}
while(p->next!=NULL){
if(p->next->a == data){
ptemp = p->next;
p->next = p->next->next;
free(ptemp);
return 1;
}
p = p->next;
}
return 0;
}
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{
struct Test *p=head;
if(p==NULL){
head=new;
return head;
}
while(p->next!=NULL){
p=p->next;
}
p->next=new;
return head;
}
struct Test *createLink(struct Test *head)
{
struct Test *new;
while(1){
new=(struct Test *)malloc(sizeof(struct Test));
printf("输入数据(输入0结束输入)\n");
scanf("%d",&(new->a));
new->next = NULL;
if(new->a==0){
free(new);
return head;
}
head=insertFromTail(head,new);
}
}
int deleteNode(struct Test *head,int data)
{
struct Test *p,*ptemp;
p=head;
if (p->a == data)
{
ptemp = head;
head = head->next;
free(ptemp);
return 1;
}
while(p->next!=NULL){
if(p->next->a == data){
ptemp = p->next;
p->next = p->next->next;
free(ptemp);
return 1;
}
p = p->next;
}
return 0;
}
int main()
{
struct Test *head=NULL;
struct Test stu1 ;
int data;
int ret;
head = createLink(head);
printf("=================插入节点前=================\n");
printlink(head);
printf("请输入要删除的节点:\n");
scanf("%d",&data);
ret = deleteNode(head,data);
if(ret){
printf("删除节点成功!\n");
}else{
printf("删除失败\n");
}
printf("=================删除节点后=================\n");
printlink(head);
system("pause");
return 0;
}
(3)改
/*
修改指定节点
*/
int alterNode(struct Test *head,int data,int newData)
{
struct Test *p = head;
while(p!=NULL){
if(p->a==data){
p->a = newData;
return 1;
}
p=p->next;
}
return 0;
}
/*
修改指定节点
*/
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{
struct Test *p=head;
if(p==NULL){
head=new;
return head;
}
while(p->next!=NULL){
p=p->next;
}
p->next=new;
return head;
}
struct Test *createLink(struct Test *head)
{
struct Test *new;
while(1){
new=(struct Test *)malloc(sizeof(struct Test));
printf("输入数据(输入0结束输入)\n");
scanf("%d",&(new->a));
new->next = NULL;
if(new->a==0){
free(new);
return head;
}
head=insertFromTail(head,new);
}
}
int alterNode(struct Test *head,int data,int newData)
{
struct Test *p = head;
while(p!=NULL){
if(p->a==data){
p->a = newData;
return 1;
}
p=p->next;
}
return 0;
}
int main()
{
struct Test *head=NULL;
struct Test stu1 ;
int data;
int newData;
int ret;
head = createLink(head);
printf("=================修改节点前=================\n");
printlink(head);
printf("请输入要修改的节点:\n");
scanf("%d",&data);
printf("请输入修改的内容:\n");
scanf("%d",&newData);
ret = alterNode(head,data,newData);
if(ret){
printf("修改节点成功!\n");
}else{
printf("修改失败\n");
}
printf("=================修改节点后=================\n");
printlink(head);
system("pause");
return 0;
}
(4)查
-在链表中查找相应的节点
int searchNode(struct Test *head,int data)
{
while(head!=NULL){
if(head->a==data){
return 1;
}
head=head->next;
}
return 0;
}
#include
#include
struct Test
{
int a;
struct Test * next;
};
void printlink(struct Test * point)
{
while(point!=NULL){
printf("%d ",point->a);
point=point->next;
}
putchar('\n');
}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{
struct Test *p=head;
if(p==NULL){
head=new;
return head;
}
while(p->next!=NULL){
p=p->next;
}
p->next=new;
return head;
}
struct Test *createLink(struct Test *head)
{
struct Test *new;
while(1){
new=(struct Test *)malloc(sizeof(struct Test));
printf("输入数据(输入0结束输入)\n");
scanf("%d",&(new->a));
new->next = NULL;
if(new->a==0){
free(new);
return head;
}
head=insertFromTail(head,new);
}
}
/*
查找相应节点
*/
int searchNode(struct Test *head,int data)
{
while(head!=NULL){
if(head->a==data){
return 1;
}
head=head->next;
}
return 0;
}
int main()
{
struct Test *head=NULL;
struct Test stu1 ;
int data;
int newData;
int ret;
head = createLink(head);
printf("=================链表数据=================\n");
printlink(head);
printf("想要查找的节点\n");
scanf("%d",&data);
ret = searchNode(head,data);
if(ret){
printf("%d在链表中\n",data);
}else{
printf("%d不在链表中\n",data);
}
system("pause");
return 0;
}