C语言基础

3.2.1 常量和变量

常量

C语言常量分为直接常量符号常量两种。

直接常量

直接常量又分为整型常量,实型常量,字符型常量和字符串常量。

1.整型常量

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

2.实型常量
实数 浮点数
(1)小数形式:
(2)指数形式:±尾数E指数。

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
510E          /* 非法的:不完整的指数  字母E或e的前后必须有数字 */
210f          /* 非法的:没有小数或指数  小数点不可以省略*/
.e55          /* 非法的:缺少整数或分数 字母E或e的前后必须有数字*/

3.字符型常量
用英文单引号括起来,只保存一个字符’a’、‘b’ 、‘*’ ,还有转义字符 ‘\n’ 、‘\t’。

4.字符串常量
用英文的双引号引起来 可以保存多个字符。

"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"			//这三种形式所显示的字符串是相同的。

内存中占用一段连续的存储单元,系统自动在字符串的尾部加上’\0’作为字符串的结束标志,因此,n个字符组成的字符串,在内存中占用n+1个字节空间可以使用sizeof运算符来计算字符串占用的内存空间大小。

字符串的长度等于该字符串中所包含的有效字符个数.如" Hello World"的长度为10.转义字符可以作为一个字符,如:“Hello World\t"的长度为11。strlen()函数来计算字符串长度。

字符常量和字符串常量在内存中的存储情况是不同的,如‘6’在内存中占1个字节,存储的是ASCII码,而”6“在内存中占2个字节,一个字节存储‘6’,另一个字节存储‘\0’。可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予给一个字符常量,在C语言中,由于没有提供字符串类型的变量,字符串一般用字符数组来解决。

符号常量

#define 标识符 常量值
符号常量不占内存 习惯上用大写标识符

变量

常变量(c99)

const int Max=100; 

符号常量不占用内存空间,在预编译时就全部由符号常量的值替换了;而常变量占用内存空间,此变量在存在期间不能重新赋值。
在对象名前使用const声明常对象,但声明时必须同时进行初始化,而且不能被更新。

标识符

参见文章:C语言编程规范 — 标识符的命名规则

3.2.2 数据类型

1 基本类型:
算术类型,包括两种类型:整数类型和浮点类型。
2 枚举类型:(enum)
它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
3 void 类型:
类型说明符 void 表明没有可用的值。
4 派生类型:
它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。

详细内容查看链接:C语言数据类型

第7章 函数

7.4 函数声明

参见链接:函数声明应该写在什么位置?main函数里面还是前面?

7.7 数组作函数参数

数组元素做函数实参,不能做形参。

传递的数组元素的

一维数组作函数参数,数组名做函数实参和形参。

传递的是数组首元素的地址。所以数组名做函数实参,形参必须是对应的数组名或是指针变量

void fun1(char *a,int length);  		//数组名 形参一维数组的声明中可以写元素个数length,也可以不写。
void fun2(char a[],int length);  		//数组名  a[]中方括号内的数值并无实际作用,编译系统对一维数组方括号内的内容不予处理
void fun3(char a[10],int length);   	//数组名

//调用时
fun1(array,n);

数组首地址传给被调用的函数后,由于是地址传递,相当于形参实参的两个数组共用同一段内存单元,所以改变形参的值就会把实参的值改变掉。值传递是不会改变形参的值的,因为形参不会占用内存,只是临时的存储单元。

二维数组作函数参数,数组名作为实参和形参。

在对形参数组声明时,必须指定列的大小,且应与实参的列大小相同
第一维的大小可以忽略,形参实参的一维大小可以不同。

int array[3][10];  //形参数组的两个维都指定    
int array[][10];  //第一维大小省略  二者都合法而且等价,但是不能把第二维的大小省略。
/* 下面写法不合法 */
int array[][];  	//不能确定数组的每一行有多少列元素 
int array[3][];  	//不指定列数就无法确定数组的结构

7.8 局部变量和全局变量

局部变量优先原则:当局部变量与全局变量同名时,在该函数或者复合语句内,局部变量优先于全局变量。

建议多使用局部变量,因为局部与全局的内存存储方式不同,全局变量存储在全局数据区中,局部变量存储在栈区。局部变量仅仅在使用时才会开辟存储单元,随函数的退出或循环退出就不存在了,而全局变量的生命期和主程序一样,随程序的销毁而销毁。

其次,如果使用了全局变量,会降低程序的可靠性和移植性。违背了程序“高内聚,低耦合”的模块要求。一般要求把C语言的函数要做成一个封闭体,除了“实参—形参”的渠道与外界产生联系以外,没有其他渠道。

最后全局变量的使用会降低程序的清晰性和可阅读性,容易出错。

7.9 变量的存储方式和周期

在这里插入图片描述

1、auto 自动变量

int f(int a)
{
	auto int b,c=3; /*定义b,c自动变量*/
}

执行完f函数后,自动释放a,b,c所占的存储单元。关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。
作用域:函数内

2、register 寄存器变量
将变量存在寄存器中,减少访问内存的次数,提高效率。但现在的编译器优化到自动将变量存放在寄存器中了,不需要程序员来指定,所以平时使用性不大。
作用域:函数内,但是会保留变量值。

3、extern 外部变量 [声明]
1)在一个文件中扩展外部变量的作用域;

#include <stdio.h>
int main(){
	extern int a;		//不加extern关键字时a=0  是声明,不是定义
	printf("a = %d",a);
}
int a = 520;		//定义

2)扩展外部变量的作用域到其他文件;
file2.c中定义了变量aint a = 520;,在file1.c中使用变量a:

#include <stdio.h>
extern a;
int main(){
    printf("a = %d\n",a);
    return 0;
}

作用域:函数内+函数外,变量值保留,随程序结束再释放。

4、static 静态变量
1) 对局部变量使用static关键字,会保留住变量的值,将其存储在静态存储区,不会随当前函数结束而释放内存单元。
2) 对全局变量使用static关键字,该变量的作用域只存在于当前文件模块。

7.10 内部函数和外部函数

内部函数:只能被本文件中其他函数调用,在函数名和函数类型的前面加上 static

//第一个文件
#include<stdio.h>
void show()
{
	printf("%s \n","first.c");
}
//第二个文件
#include<stdio.h>
static void show()
{
	printf("%s \n","second.c");
}
void main()
{        
	show();
}

运行结果:second.c

外部函数:需要调用其他源文件中的函数,调用外部函数之前,需要在当前源文件中定义外部函数,添加extern关键字。

//第一个源文件
int add(int x,int y)
{
	return x+y;
}
//第二个源文件
#include <stdio.h>
extern int add(int x,int y);
void main()
{        
	printf("sum=%d\n",add(1,2));
}

第8章 指针

8.2 指针变量

指针:内存地址信息和指向数据的变量类型—带类型的地址

指针变量:地址变量,用来存放地址(指针)。

指针变量的引用

void Pointer(){//使用指针变量
    int a=100,b=30;
    int *p1,*p2;    		//定义指针变量:类型名 * 变量名;  
    p1=&a;  p2=&b;    		//引用指针变量①:给指针变量赋值
    printf("a=%d,b=%d\n",a,b);
    printf("*p1=%d,*p2=%d\n",*p1,*p2);				//引用指针变量②:引用指针变量指向的变量
    printf("a的地址%d,b的地址%d\n",p1,p2);			//引用指针变量③:引用指针变量的值
}
//运行结果

a=100,b=30
*p1=100,*p2=30
a的地址6421980,b的地址6421976

指针变量作为函数参数

作用:将一个变量的地址传送到另一个函数中。
在被调函数的执行过程中,应使指针变量所指向的参数值发生变化,这样,函数在调用结束后,其变化值才能保留回主调函数。
函数调用不能改变实参指针变量的值,但可以改变实参指针变量所指向变量的值。

void swap1(int *pa,int *pb){
    int temp;
    temp = *pa; //将a的值先保存起来
    *pa = *pb; //将b的值交给a
    *pb = temp; //再将保存起来的a的值交给b
}
void changePionter(void){//引用指针变量交换ab的值
    int a = 5, b = 9;
    int *pa = &a, *pb = &b;  //定义变量同时初始化
    printf("a=%d, b=%d\n", a, b);
    swap1(pa,pb);
    printf("a=%d, b=%d\n", a, b);
}
void main()
{    
    changePionter();
}
//运行结果
a=5, b=9
a=9, b=5

将上述代码的调用换成swap2()函数则不会改变ab的值。
下面这段代码交换了两个指针变量的值,但是由于调用函数是值传递方式,所以main()函数中指针变量并不会改变值。

void swap2(int *pa,int *pb){
    int *temp;
    temp = pa; //将a的地址先保存起来
    pa = pb; //将b的d交给a
    pb = temp; //再将保存起来的a的值交给b
}

//运行结果
a=5, b=9
a=5, b=9

8.3 通过指针引用数组

数组元素的指针:数组元素的地址

void PionterArray(){
    int a[10]={14,3,4,5,6,8,9,9,12,13};
    int b[10]={21,3,4,5,6,8,9,9,12,13};
    int *p;p=&a[0];         //a[0]的地址<=>int *p=&a[0]; 
    int *q=b;               //程序中的数组名代表首元素的地址 代表把数组b的首元素(下标0)地址赋给指针变量q
    printf("a[0]=%d,b[0]=%d\n", *p,*q);
}

引用数组元素时的运算

当指针指向数组元素时,可以对指针进行加减运算。

  • 指针的加减
void PionterOperation(){  
    /* 指针自加自减运算 */
	int i, *iPointer = &i;
	short j, *sPointer = &j;
	double k, *dPointer = &k;
	
	scanf("%d%d%d", iPointer, sPointer, dPointer);
	
	printf("\nint:\n");
	printf("p   = %d\n", iPointer);
	iPointer ++;
	printf("p++ = %d\n", iPointer);
	
	printf("\nshort:\n");
	printf("p   = %d\n", sPointer);
	sPointer ++;
	printf("p++ = %d\n", sPointer);
	
	printf("\ndouble:\n");
	printf("p   = %d\n", dPointer);
	dPointer ++;
	printf("p++ = %d\n", dPointer);
}

//输入:
100 300 500
//运行结果
int:
p   = 6421972
p++ = 6421976				//基本整形变量i在内存中占4个字节

short:
p   = 6421970
p++ = 6421972				//短整型变量j在内存中占2个字节

double:
p   = 6421960
p++ = 6421968				//双精度变量k在内存中占8个字节

  • 数组指针的加减运算
    如果指针变量p已经指向数组中的第一个元素,则p+1指向同一数组中的下一个元素,p-1指向同一数组中的上一个元素。
    p+1实际上是p+1*d,d表示该数组元素所占的字节数。
void PtArrayOperation(){

    int urn[5] = {100,200,300,400,500};
    int *ptr1,*ptr2,*ptr3,*ptr4,*ptr5,*ptr6;

    printf("urn[5] = { ");
    for(int j=0;j<5;j++){
        printf("%d ",urn[j]);
    }
    printf("};\n");

    ptr1 = urn;	            //将数组首地址赋给指针
    ptr2 = &urn[2];	        //将数组中第三个元素的地址赋给指针
    ptr3 = ptr1 + 4;        //指针加法 数组元素urn[4]的地址
    ptr4 = ptr2 - 1;        //指针减法
    ptr5 = ptr2 - 3;        //指针指向数组第一个元素之前,那么它是非法的。
    ptr6 = ptr2 + 3;        //指针指向数组最后一个元素之后,仍是合法的,但不允许对其执行间接访问操作。

	printf("ptr1   = %d\n", *ptr1);
	printf("&urn[2]= %d\n", *ptr2);
	printf("ptr1+4 = %d\n", *ptr3);
	printf("ptr2   = %d\n", *ptr4);
	printf("ptr2-3 = %d\n", *ptr5);
	printf("ptr2+3 = %d\n", *ptr6);
}
//运行结果
urn[5] = { 100 200 300 400 500 };
ptr1   = 100
&urn[2]= 300
ptr1+4 = 500
ptr2   = 200
ptr2-3 = 0
ptr2+3 = 0
  • 数组指针的关系运算
    前提:两个指针都指向同一数组的元素。如果指向两个不同数组的指针进行求差运算可能会得出一个值,或者导致运行时错误。
    p2-p1的结果是两个地址之差除以数组元素的长度。通常用来表示两个指针所指的元素之间差几个元素。
void main()
{    
    int urn[5] = {100,200,300,400,500};
    int *ptr1,*ptr2;

    ptr1 = &urn[0];	            //将数组首地址赋给指针
    ptr2 = &urn[2];	        	//将数组中第三个元素的地址赋给指针

    printf("ptr2-ptr1 = %d\n", ptr2-ptr1);
}
//运行结果
ptr2-ptr1 = 2
注意:两个地址不能相加,比如ptr2+ptr1是无实际意义的。

通过指针引用数组元素

(1)指针法 * (a+i) 或 * (p+i)或 p[i] (2)下标法 a[i]
数组名代表的是数组中首元素的地址。在程序编译时,a[i]是按*(a+i)处理的,即按数组元素的首地址加上相应位移量i找到新元素的地址。而p=a,即p是指向数组a的首元素的地址,因此是等价的。从这里可以看出,[ ]实际上是变地址运算符,即将a[i]按a+i计算地址,然后找此地址单元中的值。

void main()
{    
    int urn[5] = {100,200,300,400,500};
    int *ptr1;
    ptr1 = urn;	           
    printf("指针法:*(ptr1+1) = %d\n", *(ptr1+1));
    printf("指针法:*(urn+1)  = %d\n", *(urn+1));
    printf("指针法:ptr1[1]   = %d\n", ptr1[1]);			//当指针变量指向数字元素时,指针变量可以带下标。
    printf("下标法:urn[1]    = %d\n", urn[1]);
}
//运行结果
指针法:*(ptr1+1) = 200
指针法:*(urn+1)  = 200
指针法:ptr1[1]   = 200
下标法:urn[1]    = 200

注意:
1.指针指向数组元素时要保证指向数组中有效的元素。
指针指向数组第一个元素之前,那么它是非法的。
指针指向数组最后一个元素之后,仍是合法的,但不允许对其执行间接访问操作。
2.当指针变量指向数字元素时,指针变量可以带下标。对p[i]处理成*(p+i)。尽量避免这种写法。

*p++的运算,优先级相等,结合方向是自右向左,要注意运算结果。

void main()
{    
    int urn[5] = {100,200,300,400,500};
    int *ptr1 = &urn[0];
    int *ptr2 = &urn[0];
    int *ptr3 = &urn[0];
    int *ptr4 = &urn[2];
    printf("ptr1 = %d\n", *(++ptr1));   //ptr1 = 200     p的指向先++,再引用
    printf("ptr2 = %d  ", *(ptr2++));   //ptr2 = 100     p的指向先引用,再++      ptr2 = 200  	*(ptr2++) <=> *ptr2++
    printf("ptr2 = %d\n", *ptr2);       //ptr2 = 200
    printf("ptr3 = %d\n", ++*(ptr3));   //ptr3 = 101     p所指向的数组元素urn[0]加1
    printf("ptr4 = %d  ", *(ptr4--));   //ptr4 = 300     p的指向先引用,再--    	ptr4 = 200
    printf("ptr4 = %d\n", *ptr4);       //ptr4 = 200     
}

8.4 数组名做函数参数

前面7.7 章节已经讲过这部分内容,现在主要加以理解。

程序编译时是将形参变量a按指针变量处理的,所以以下写法等价。

void fun1(char *a,int length);  		//数组名 形参一维数组的声明中可以写元素个数length,也可以不写。
void fun2(char a[],int length);  		//数组名  a[]中方括号内的数值并无实际作用,编译系统对一维数组方括号内的内容不予处理

变量名的传递:值传递,形参类型变量名,传递的变量的值 ,不能改变实参的值
数组名的传递:址传递,形参类型数组名或指针变量,传递的数组首元素的地址,可以改变实参的值

实参的数组名是一个固定的地址,或者说是指针常量,形参数组名不是固定的地址,而是按照指针变量处理。

实例:
将数组a中n个整数按相反顺序存放。array[10] = { 3 7 9 11 0 6 7 5 4 2 }; => array[10] = { 2 4 5 7 6 0 11 9 7 3 };

实参是数组名 数组名做形参

//数组名做形参  实参是数组名
void inv1(int a[],int n){
    //i:左边的下标 j:右边的下标 m:中间下标值 temp:交换的中间值
    int temp,i,j,m;
    m = (n-1)/2;
    for(i=0,j=n-1;i<=m;i++,j--){
        //i是第一个元素,j是最后一个元素,两两交换。每次交换以后i往后一个元素,j往前一个元素。直到中间m下标位置。
        temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
}
void main(){

    int i,array[10]={3,7,9,11,0,6,7,5,4,2};
    
    printf("array[10] = { ");
    for(int i=0;i<10;i++){
        printf("%d ",array[i]);
    }
    printf("};\n");

    inv1(array,10);

    printf("array[10] = { ");
    for(int i=0;i<10;i++){
        printf("%d ",array[i]);
    }
    printf("};\n");
}

实参是数组名 指针变量做形参

//指针变量做形参  实参还是数组名
void inv2(int *a,int n){
    int temp,*p,*i,*j,m = (n-1)/2;
    i=a;j=a+n-1;            //i:指向左侧数组元素   j:指向右侧数组元素  
    p=a+m;                  //p:中间的元素a[m]
    for(;i<=p;i++,j--){
        //i是第一个元素,j是最后一个元素,两两交换。每次交换以后i往后一个元素,j往前一个元素。直到中间m下标位置。
        temp=*i;
        *i=*j;
        *j=temp;
    }
}

指针变量做实参 形参使用数组名或指针变量

指针变量做实参,必须先使指针变量有确定值,指向已经定义的对象。
不管形参是数组名还是指针变量的写法,实际上都是指针变量

void main(){

    int i,array[10]={3,7,9,11,0,6,7,5,4,2};
    int *p = array;
    
    printf("array[10] = { ");
    for(int i=0;i<10;i++){
        printf("%d ",array[i]);
    }
    printf("};\n");

    inv1(p,10);             //指针变量做实参,必须先使指针变量有确定值,指向已经定义的对象
    //inv2(p,10);             //指针变量做实参,必须先使指针变量有确定值,指向已经定义的对象

    printf("array[10] = { ");
    for(int i=0;i<10;i++){
        printf("%d ",array[i]);
    }
    printf("};\n");
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值