C语言注意点总结(updating)——《C Primer Plus》笔记系列1

本文是关于C语言编程的一些重要知识点的总结,包括利用scanf的正确使用方式、编程规范、函数参数的作用域、指针运算的特殊性、const常量的使用、指针兼容性和数组操作等。同时,讲解了预处理器、位操作、内存管理和结构体、枚举等概念,是C语言学习者的实用参考。
摘要由CSDN通过智能技术生成
1、利用scanf来得到输入的数字数据时,可利用scanf返回输入数据个数来判断真假。

eg:while((status=scanf(“%d”,&code))!=1 );该语句的意思是当scanf读入数据数量为1时,才执行while后面的代码。

  • 零散点注意
    如下代码:
#include <stdio.h>
struct name{
    char lname[3];
    char fname[3];
    int n;
};

int main()
{
    struct name name1;
    name1.lname="AD";
    name1.fname="SD";
    name1.n=9;
    printf("%s %s %d",name1.lname,name1.fname,name1.n);
}

/*--------------------------------------------------------- 
1)注意:name1.lname="AD";报错:assignment to expression with array type。因为lname已经被定义为数组了,C99标准中不允许将字符串(实际上是一个指针变量) 赋值给数组。
2)另一方面解释:原因是不能修改数组名称引用的地址。当你将字符串(实际上是一个指针变量) 赋值给数组,实质上就是在尝试改变数组的地址
**然而,当利用“声明定义”时则可以,如:char name[3]="AD"---------------------------------------------------------*/
2、编程习惯:声明指针时,星号与变量之间要有空格,即int * p。而表示指向变量时,则无,即*p。
3、不同函数的参数为该函数独有,即使与其他函数中有变量名一致情况,计算机会把所有同名变量分别视为不同变量。若要访问其他函数变量数据,要用到指针访问地址。
4、注意指针(16进制)运算和普通的16进制运算不一样。

eg:int a=1,b=2;p1=&a,p2=&b;假设输出为p1=0028FF04,p2=0028FF00。有代码 printf(“%d”,p1-p2);会输出1,因为声明时指针p1和p2为相邻指针,也就是p1是第一个指针,p2就是第二个。然而,16进制运算有0028FF04-0028FF00=4(因为一个int占4个字节)。4不等于1啊啊啊。。。

5、注意:不要对未初始化的指针赋值

eg:int * p;*p=5 //严重错误!!!
所以,每次声明指针后,确保之后不久就被赋值,不然变成“野指针”就不好了。
eg:正面例子:int p=1;int * pt=&p;

6、const的应用(难点)
1)const double PI=3.1415926;

该语句意思:声明PI为常量,等同于

#define PI 3.1415926

此时若之后对PI进行改动,编译器会报错
eg:

const double PI=3.1415926
PI=3    //编译错误
PI=3.1415926    //注意,也会编译错误

即const声明常量后,无法修改PI值

2)注意精髓:
const type p;   
//type=int,double等,p可为普通变量名p,指针(如*p)等

该声明结果为:p(可以为普通常量,指针*p等)为只读常量,无法修改。

3)多种const声明
double ar[ ]={1,2,3,4,5};
const double * p=ar;//ar即为数组首地址
*p=10;  //不允许
p[2]=222;//不允许
ar[0]=2;//可以
  • 以上情形为*p为常量,即不能通过指针p来改变数组ar的值,但可以有ar[0]=2,即可以用ar来改变数组值。
double rates[ ]={1,2,3,4,5};
const double locked[4]={1,2,3,4};
const double * pc=rates;//合法
pc=locked;//合法
pc=&rates[3];//合法
  • 以上情形:const int * p为指向常量的指针,将常量或非常量数据赋给指向常量的指针是允许的。
double rate[]={...}
const double locked[]={...};
double * p=rates;
p=locked;   //非法
p=&rates[3];    //合法
  • 以上情形:只有非常量数据的地址才会被赋给普通的指针。因为,如果const 常量赋给普通指针的话,该指针就可以改变该常量了。
double rates[]={...};
double *const p=rates;  
//const p表明指针p的值为常量,即p的值(指向地址)不可以被改变,但可以通过*p改变p所指向地址的变量的值.
const double * const pc=rates;
//该指针既不可以改变地址,也不可以改变所指向数据值(即*pc)
4)const的好处

在函数参数声明中定义使用const,不仅可以保护数据。还能使函数可以使用声明const的数组
eg:

void show(const double ar[],int n)
7、指针兼容性

eg:

int * pt;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int ** p2;  //指向指针的指针
//有结论
pt=&ar[0][0];   //1、都指向int
pt=ar1[0];      //2、都指向int
pt=ar1;         //3、非法
pa=ar1; `       //4、都指向int[3]
pa=ar2;         //5、非法
p2=&pt;         //6、都指向int
*p2=ar2[0];     //7、都指向int
p2=ar2;         //8、非法
  • 注意(解释):pt为指向int的指针,pa为指向一个含有三个元素的一位数组的指针。有点难解释。。自己想通一遍。。对3,有ar1为指向三个int值构成的数组,与pt不同类型;对5,有ar2为指向指向含有两个int值的数组,与pa类型不同;对8,p2为指向指针的指针,与ar2不符合类型。。。
  • 注意:一般地,声明N维数组的指针,除了最左边的方括号可以留空外,其他应该填写数据。首个方括号表明这是个指针,其他方括号数据表明数据类型
    eg:

int sum4d(int ar[ ][12][20][30],int rows);
//等效于
int sum4d(int (*ar)[12][20][30],int rows);

8、puts函数注意:puts函数每次输出字符串后,自动换行。
9、声明字符串:声明字符串数组时记得确保元素个数至少比字符串长度+1(用于容纳\0 ),未被使用元素均被自动初始化为0。

eg:char st[3]=”as”;

10、数组与指针

对声明

char heart[]="I love Tiller";
cahr *head="I love Miller";
1)数组名heart为常量,指针名head为变量。因此++heart(–heart等)均为错误的,++head为正确的。增量运算符只能应用于变量。
2)两者均可使用数组符号和指针加法。

eg:
对指针head

head[0]==I;
head[7]==M;

对数组heart

*heart==head[0]==I;
*(heart+7)==T;
3)假定希望head与heart相同,有
head=heart  //编译通过
heart=head  //错误

这种情况类似于赋值语句x=3以及3=x,赋值语句左边必须为变量或左值(lvalue),且数组名heart为常量,指针名head为变量。
ps:head=heart,不会使原来head指向的Miller字符串消失,只是如果前面没有保存Miller字符串的地址的话,head指针就无法再次指向Miller字符串了。

11、注意:

注意以下错误用法:

char * word="frame";
word[1]='l';

这种用法严格来说将会编译错误,dev C++、Codeblock以及VS2015中都会显示“程序异常中断”。因此,建议初始化一个指向字符串的指针时使用const

const char * word="frame";
12、储存类、链接&内存管理
1)5种储存类

自动:时期:自动;作用域:代码块;链接:空;声明方式:代码块内。
寄存器:时期:自动;作用域:代码块;链接:空;声明方式:代码块内,使用关键字register。
具有内部链接的静态:时期:静态;作用域:文件;链接:外部;声明方式:所有函数之外。(一个文件定义声明赋值,其余文件引用声明extern)
具有内部链接的静态:时期:静态;作用域:文件;链接:内部;声明方式:所有函数之外,使用关键字static。
空链接的静态:时期:静态;作用域:代码块;链接:空;声明方式:代码块内,使用关键字static。

2)注意:

(1)内层代码块定义的名字是内层代码块所拥有的变量,称之为内层定义覆盖(即若内部代码变量名与外部一致时,用内部的),当运行离开内层代码时,外部变量重新恢复作用。
(2)register:寄存器变量关键字register**只是请求,不是命令,一般两个register变量可以,多个的话将会自动把多余的设置为自动变量。同时,register对int型可以设为寄存器变量,然而对float、double型则可能不行。而且,记住寄存器是在CPU中的,所以register变量没有地址!!!所以不能取址(即指针操作),只有在内存中的变量才有地址。**
(3)static用法:

#include <stdio.h>
/*...
...
...*/
void try(void)
{
    int fade=1;
    static int stay=1;
    printf("fade=%d and stay=%d\n",fade++,stay++);
}
/*注释:对try连续执行三次(即main里面有for),有输出
//  fade=1 and stay=1
    fade=1 and stay=2
    fade=1 and stay=3  //

即有stay变量是定了的,不像fade那样每次经过try函数都会初始化。
也就是,其实,stay在try函数编译时只进行一次初始化。且语句static int stay=1实际只在第一次进行编译,调试时会发现在第二、三次时程序跳过了那一步。
(4)malloc&free
malloc是接受一个参数:所需内存字节。然后malloc找到可用内存的一个大小合适内存块并返回内存块首地址
eg:

double * ptd;
ptd=(double*)malloc(30*sizeof(double));
//注意:malloc返回void型通用指针,即可以是数组指针,char型指针等。所以此处要类型转换

Attention:一般地每用一次malloc则要用一次free函数释放malloc分配的内存,free()的参数是malloc返回值(即分配的内存块的首地址)。
free( )’s importance:
Now introduce an example to stess the importance of function “free( )”
eg:

...
int main()
{
double glad[2000];
int i;
...
for(i=0,i<1000;i++)
    gobble(glad,2000);
...
}

void gooble(double ar[],int n)
{
double * temp=(double*)malloc(n*sizeof(double));
...
/* free(temp); */   //假如忘记使用free//

If you had forgot to use function “free”,so if you firstly transfer(调用)malloc and then malloc assign 16000 bytes(2000 double variables with everyone occcupying 8 bytes) to the program. However,you didn’t use free,so when the program finishs,the pointer temp will disapper and apprently,you can’t visit those 16000 bytes memory block.SO,if you use for to handle with function malloc without using function free,there may be an amount of memory block occupied and worstly ,you can’t use pointers to visit them !!! With times you handle it increasing, lots of memory can’t be used,resuting a phenomenon named memory leak(内存泄漏) .(Chinglish~~)
(5)类型限定词
const:对于用在指针的const,位在 * 左边任何位置的const使得数据成为常量,而位于 * 右边任何位置的const使得指针自身(即指向的地址)变为常量。
volatile(搞不懂怎么用):表明数据除了可被程序修改以外还可以通过其他方式修改,其目的是警示编译器优化时不要做出相反的假设。
restrict:限定的指针被认为时提供了对其所指向的数据块的唯一访问途径。
(6)malloc&calloc区别(有待以后完善)
malloc:调用形式: (类型说明符*) malloc (size) 功能:在内存的动态存储区中分配一块长度为”size” 字节的连续区域。
calloc:调用形式: (类型说明符*)calloc(n,size) 功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。
区别:calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。

13、结构体struct、联合union&枚举类型enum
1)数组与结构的不同
  • 和数组不同,结构的名字不是该结构地址,要用&
    eg:
struct book{...};
struct * p=&book;   //对的 
struct * p=book;    //错误
  • 结构允许赋值,数组不行
struct book{...};
int temp[2]={...};
struct rem;
int tem[2];
rem=book;   //允许
tem=temp;   //不允许
--------------------
//允许结构初始化为另一个同样类型结构
struct name right_field={...};
struct name captain=right_field;    //允许
2)伸缩型数组成员(C99)

规则:

  • 伸缩型数组成员必须是最后一个数组成员
  • 结构中必须至少有一个其他成员
  • 伸缩型数组就像普通数组一样声明,除了它的方括号内是空的外
    eg:
struct flex
{
    int count;
    double average;
    double scores[];
}
  • Attention:然而,你不能对scores做任何事情,因为它还没被分配内存。实际上,C99只是想让你声明一个指向flex的指针,并用malloc分配空间,以存放struct flex结构的常规内容和伸缩型数组成员。
    eg:
struct flex *pf;
pf=malloc(sizeof(struct flex)+5*sizeof(double));
//注意:此处为flex中的count、average分配内存加上5个double型数值的数组。
3)union联合
  • Attention1:联合(union)与结构(struct)的不同:union变量是只能储存一个值,即每次只能储存一个值
    eg:
struct name{char lname[5];char fname[5];int n;};
union thing{char t1[5];char t2[5];int m;};
/* 以下为对name结构体声明,表示该结构储存了字符串Lau、Eajack和int型变量2 */
name.lname="Lau";
name.fname="Eajack";
name.n=2//以下为对union变量声明,注意不同
union.t1="Apple";
union.t2="Air";     /* 此处清除字符串Apple,储存Air,使用5字节(注意原本声明5字节)*/
union.m=3;      //清除Air,储存3,使用4字节。
/* 即,union变量不能同时储存两个变量,如不能同时储存t1和t2,或者,t1和m */

/*-------------------------------------------------------- 
More:
1、存储多个成员信息时,编译器会自动给struct第个成员分配存储空间,struct 可以存储多个成员信息,而Union每个成员会用同一个存储空间,只能存储最后一个声明成员的信息,且它所有的元素共享同一内存单元,所被分配的内存size由类型最大的元素size来确定
2、在任何同一时刻,Union只存放了一个被先选中的成员,而结构体的所有成员都存在。

---------------------------------------------------------*/
  • Attention2:union与struct的相同点:
    1)声明一致,都可以用->运算符
4)enum枚举类型
  1. 声明
    eg:
//声明与struct、union类似
enum color{red,blue,green};
enum color getall;
  • 注意:枚举类型为int型,分隔不是分号,是逗号
  • 解释:enum color和struct color,union color一样,均可视为类型名,这里表示getall为该类型名的一种变量(类型名如:int,double)。enum color getall 表示getall 可能值为red,blue,green的其中一种。
  • 默认值与指定值:枚举列表中常量被指定为整数值0、1、2等。除非指定声明。
    eg:
enum kids{A,B,F};
//其中,F默认为2。
enum color{red=1,blue=90,green=37};
//以上为制定赋值
enum thing{cat,pen=100,rat,apple};
//若只对一个变量赋值,后面的值默认为该值+1累加。因此,有cat=0;pen=100;rat=101;apple=102。
14、再论指针、函数&数组

对声明的总结(重点

int board[8][8];    //数组的数组,即二维数组
int ** pst;         //指针的指针,即二维指针
int * risk[10];     //注意:具有10个元素的数组,每个元素都是指向
                    //int的指针                    
int (* rusk)[10];   //一个指针,指向含有10个元素的int数组
int * oof[3][4];    //一个3*4的数组,每个元素都是指针。类比int 
                    //* risk[10]
int (* uuf)[3][4];  //一个指针,指向一个3*4的int数组
int (*uof[3])[4];   //一个具有3个元素的数组,每个数组都是一个指向
                    //具有4个元素的int数组的指针

搞懂:

修饰符含义
*表示一个指针
()表示一个函数
[ ]表示一个数组
备注:1、[ ]与()有相同优先级,并高于*修饰符
2、[ ]与()均为从左到右结合

eg:对以下进行描述

int * oof[3][4]

  • [3]和[4]都比* 有更高的优先级,且为从左到右规则,所以先看[3]再看[4],知道oof是一个3*4数组。又由于前面有 *,因此oof所有元素均为指针!!!

对于下列函数说明:

char * fump();          //返回指向char的指针的函数
char (* frump)();       //指向返回类型为char的函数的指针
char(* flump[3])();     //由3个指针组成的数组,每个指针指向返回类
                        //型为char的函数

函数指针的声明

void ToUpper(char *);
void (* toupper)(char *);
//等价声明,其中toupper是一个函数指针。
//因此,要声明一个函数指针,只需先声明原函数,在用(* pst)取代原数
//名称即可
//注意:void * toupper(char *)是错误的,因为( )优先级比*高,
//因此,该声明表明toupper是返回一个指针的函数

通过函数指针调用不同函数(类似,通过指针访问不同变量。)

void(*pf)(char *);  //声明函数指针
void ToUpper(char *);
void ToLower(char *);
int round(double);
pf=ToUpper;             //合法,函数名是函数地址
pf=ToLower( );      //无效,ToLower不是地址,ToLower才是
pf=round                //无效,不匹配的函数类型
15、bitwise位操作
  • 注意点:
a~=b;      //取反。编译错误,没有“ ~= ” !!!
a|=b;       //位与。允许,等价于a=a|b
a&=b;       //位或。允许
a^=b;       //位异或。允许
a<<=b;      //左移。允许
a>>=b;      //右移。允许
  • 移位运算符
num<< nnum乘以2的n次幂
num>>n若num>0,则用num除以2的n次幂

移位运算符类似于10进制移动小数点时乘以10或除以10。不同的是,2进制左移是乘以,10进制是除以,相反。

16、预处理器与宏
  1. 注意define宏与函数的区别
    Attention:对define,应使用较多的()来避免错误。同时,避免在宏中使用自增运算符(++)、(–)

对代码:

#define SQ(X) X*X
#define PR(X) printf("The result is %d.\n",X)
int main()
{
    int X=4;
    int z;

    printf("x=%d\n",x);
    z=SQ(x);
    printf("Evaluating SQ(x):");
    PR(z);

    z=SQ(2);
    printf("Evaluating SQ(2):");
    PR(z);

    printf("Evaluating SQ(x+2):");
    PR(SQ(x+2));
}

以下为输出:

x=4
Evaluating SQ(x): ...16
Evaluating SQ(2): ...4
Evaluating SQ(x+2): ...14
  • 发现有输出 4+2)2=14 ,明显是错误的。因为,预处理器不进行计算,而只是对字符串的普通替换,即有 x+2x+2=4+24+2=14 .因此,要声明为

define SQ(X) (X)*(X)
//这样就可以了
//又由于对除法同样有如此现象,应再改为
define SQ(X) ( (X) *(X) )

17、零碎点
  1. malloc要和if,free连着一套用
    eg:
int n=2;
char * s;
s=(char *)malloc( n*sizeof(n) );

if(s==NULL)
    printf("Error in distrbuting storage!!");
else
{...}

...
free(s);
...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值