目录
6关键字总结:
6.1define(宏定义)
6.2const(恒定)
6.3 sizeof
6.4static(静态)
6.4.1static修饰局部变量
6.4.2static修饰全局变量
6.4.3static修饰函数
6.4.4在C++等面对对象编程语言中
6.5 auto(自动变量)
6.6 4个与储存类型相关的关键字总结
6.6.1.auto
6.6.2.extern
6.6.3.register
6.6.4. static
6.7 typedef
32个关键字
char,short,int,long,float,double,
unsigned,signed,struct(结构体),union(共用体),enum(枚举),void
if,else,switch,case,default,for,do,while,break,continue,goto,return
auto(自动变量),extern(外部类型变量),register(寄存器类型变量),static(静态类型变量),const
sizeof,typedef,volatile
#define NUM 100
预处理部分,会将程序中所有的NUM全部单纯的替换为100,只是单纯的替换,不做其他判断。
以上过程称为宏展开
说明:
有一种特殊的宏定义:一般称为宏函数
偶尔出现较为简单的函数操作,可以使用宏函数,效率高,不会去动用多余空间
但是也请看清楚宏函数的本质,如上文所述,只是单纯的进行文本替换而已
如果打算宏代替函数,以此来提高程序的运行速度,假如在程序中只使用一次宏对程序的运行时间没有太大提高
#define SUN(x,y) ( (x) * (y) )
定义时还请务必注意,因为只是单纯的文本替换,所以在(x)* (y)外面还需要加一层括号。
说明:
我们再看一个示例:
#define SUM(x) 3*x*x+1
int main()
{
int i=5,j=8;
printf("%d\n",SUM(i+j));
return;
}
3*i+j*i+j+1=3*5+8*5+8+1=64
define,就是单纯的就是替换内容,不进行其他操作
题目1:
若有宏定义:#define S(a,b) t=a;a=b;b=t由于变量 t 没定义,所以此宏定义是错误的。请问这句话的说法是正确的吗?
错误,宏定义不做语法检查,单纯的替换内容,编译时会显示t未定义,宏定义的时候不会检查错误。
题目2:
定义宏#define DECLARE(name, type) type name##_##type##_type,
则DECLARE(val, int)替换结果为()
##是一种分隔连接方式,它的作用是先分隔,然后进行强制连接
“name”和第一个“_”之间被分隔了,所以预处理器会把name##_##type##_type解释成4段:“name”、“_”、“type”以及“_type”,name和type会被替换,而_type不会被替换
所以,结果是
int val_int_type
const只对它左边的东西起作用 , 唯一的例外就是const本身就是最左边的修饰符,那么它才会对右边的东西起作用
const定义常量(在C中不稳定)
const int a = 10;//const定义不稳定常量
之所以不稳定,是因为以上方式使用const,在C中,是可以使用一级指针对变量a的值进行修改的。
const int a = 10;//使用const定义不稳定常量
int* p = &a;//将不稳定常量a的地址赋给int类型的指针p,及p指向a的地址
*p = 100;//对a进行修改,是可以进行如此操作
//所以综上所述,使用const定义常量在C中不稳定
const修饰指针类型:
1:可以重新赋值,但是不能访问内容
int a = 10;
int b = 20;
const int * p = &a;
p = &b;//成立
*p = 100;//不成立
注意,此时的const后面跟的是指针的定义及赋值过程,const此时修饰指针类型,就const作用于int *
对指针进行重新赋值是可以的,但是无法改变指针所指向地址的内容;
被const修饰的指针类型,此时是无法修改内存中的数值的。
2:可以修改内容,但是无法改变指向(引用声明的本质,即指针常量)
int a = 10;
int b = 20;
int * const p = &a;//只能在此时进行赋值
*p = 100;//成立
p = &b;//不成立
注意,此时的const后面跟的是指针变量,const此时修饰指针变量,就const作用于p
对指针进行重新赋值是不允许的,指针指向是固定的,但是可以改变指针所指向地址的内容;
被const修饰的指针变量,此时是无法修改指针的指向。
那么综上,定义一个指针,其指向无法被修改且指向内存中的内容也无法修改,定义如下:
const int * const p = &a;//注意,只能在此时进行赋值
但是如此定义依旧不稳定,我们可以通过二级指针,对其指向的内存中的内容,进行修改。
const修饰全局变量时,存储在常量区,不能修改。
const修饰局部变量时,放在栈区,是可以修改的,通过指针。
const也常用于函数结构参数的修饰:
void Function(const int i,const double j,const struct MODE * Test)
{
.......
}
在一般传递占用较大内容的数据时,多选用指针传递,快捷方便,但是当函数只是使用这段数据而不是修改时,会出行一个风险
当指针成为参数时,效率很高,但是副作用也很明显,就是有能会出现失误,导致原始数据被修改
所以此时,使用const关键字,使得其无法被修改。高效率的同时,加强了程序的稳定性。
虽然依旧可以使用二级指针对其进行修改,但是如此明显的意图与参数加const矛盾
const此时更多提供的是一种声明,防止意外,这个参数,请不要修改,改了也没有意义。
在C++中,const会更加稳定和适用
const修饰指针的两种形式:
指针常量:关键字顺序为 * 、 const(和中文顺序一致), 例如 int * const a,表示指针a是一个常量,初始化后不可更改指向(永远指向某个对象),但是指向的对象的值可以修改,如*a=10;
常量指针:关键字顺序为 const、* ,例如 const int * a (等同与int const * a) ,表示指针a所指向的对象是个常量(其值不可以改变),但指针a可以指向其它对象,如 *a=10;是错误的,a = b;是可以的
(来自牛客网@路飞的小伙伴9)
const一般都是修饰自己左侧最近的内容,左侧没有内容的时候,修饰右侧全部的内容,所以 * const 修饰的是指针,规定了指针的指向,const int * ....,修饰的就是值本身了。
既然要固定指针的指向,必然是要修饰指针,那么指针常量,自然是int * const。
sizeof是C语言中的一个操作符(operator),不是函数调用
sizeof只关心一件事儿,就是数据类型,及获取某个数据类型所占用空间的字节数。
sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数。
其返回值是一个无符号整形(unsigned int)
括号里面甚至可以不是变量:
printf("size_int=%d",sizeof(int));
printf("size_short=%d",sizeof(short));
printf("size_double=%d",sizeof(double));
输出结果:
size_int=4
size_short=2
size_double=8
sizeof(常规变量);
如上所述,输出该数据类型所占用的内存大小,即多少个字节(Byte)。
sizeof(指针);
printf("size_int=%d",sizeof(int *));
printf("size_short=%d",sizeof(short*));
printf("size_double=%d",sizeof(double *));
printf("size_int=%d",sizeof(char *));
32位系统下,以上程序的输出结果均为4,64位为8;
sizeof(数组名);获取整个数组所有元素在内存中占有的内存空间大小。
int arr[] = {1,2,3};
printf("arr:%d",sizeof(arr));
输出结果是4x3 = 12;
int类型占有4个字节,arr数组中共3个元素,总计12字节(Byte)。
但是,当数组名作为指针在函数中传递时,数组名会降级成同类型的指针。
int main()
{
int arr[] = {1,2,3};
sizeforarr(arr);
printf("arr = %d",sizeof(arr));
return 0;
}
void sizeforarr(int test[])
{
printf("test = %d",sizeof(test));//将会降级成为int类型的指针
}
输出如下:
test = 4
arr = 12
可以使用sizeof()求数组的长度(使用时,主要数组名是否降级)
int arr[] = {1,2,3};
int len_arr = sizeof(arr)/sizeof(arr[0]);
sizeof(字符串数组名);
char str[]="hello";
printf("str = %d",sizeof(str));
输出:
str = 6
C语言会自动在在双引号""括起来的内容的末尾补上"\0"代表结束,字符串结束符占用一个字符。
以下几篇文章中关于sizeof的阐述也非常清晰,写的很好。
https://blog.csdn.net/jollyhope/article/details/1895357
https://blog.csdn.net/weixin_40087851/article/details/82190289
https://blog.csdn.net/u013812502/article/details/81198452
c语言中static关键字用法详解:https://blog.csdn.net/guotianqing/article/details/79828100(主要参考,文章写的很好)
被声明为静态类型的变量,无论是全局的还是局部的。
都存储在全局区(静态区)(Global data Segment)中,其生命周期为整个程序。
静态变量如果没有被初始化,则自动初始化为0,只能够初始化一次。
静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。仅仅初始化一次,之后一直保留,保存在静态区/全局区中。
且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。
主函数:
...........
for(int i = 0;i<10;i++)
{
StaticFunction();
}
...........
void MainWindow::StaticFunction()
{
static int a = 10;
a++;
qDebug()<<"a"<
输出:
对比一下普通局部变量:
主函数:
...........
for(int i = 0;i<10;i++)
{
StaticFunction();
}
...........
void MainWindow::StaticFunction()
{
int a = 10;
a++;
qDebug()<<"a"<
输出:
可见,静态局部变量的效果跟全局变量有一拼,但是位于函数体内部,就极有利于程序的模块化了。
全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。
普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。
也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
函数声明为static,除了对该函数声明所在的文件可见外,其他文件都无法访问。
static函数的作用域仅在本文件,在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数。其特性如下:
静态数据成员和静态成员函数:https://blog.csdn.net/qiana_/article/details/82083313
静态数据成员
在类内数据成员的声明前加上static关键字,该数据成员就是类内的静态数据成员。其特点如下:
同全局变量相比,使用静态数据成员有两个优势:
静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性
可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能
静态成员函数
与静态数据成员类似,静态成员函数属于整个类,而不是某一个对象,其特性如下:
静态成员函数没有this指针,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数
出现在类体外的函数定义不能指定关键字static
非静态成员函数可以任意地访问静态成员函数和静态数据成员
参考:浅析C语言auto关键字和C++ 中的auto关键字https://blog.csdn.net/LiuBo_01/article/details/80752734
C语言中,auto关键字的作用:
//C
auto int val;//和const。static一样,都是修饰int类型的变量val的
auto val;//这就是没有给数据类型的情况,C会自动将其变成int类型
在C++中:auto关键字是一个类型说明符,通过变量的初始值或者表达式中参与运算的数据类型来推断变量的类型
由于,需要编译器推断变量或表达式的类型,所以,auto定义的变量必须初始化
使用auto也能在一条语句中声明多个变量。因为一条语句声明时只能有一种基本数据类型,所以该语句中的所有变量初始基本数据类型必须一样,例如:
auto i = 0, *p = &i; //正确:i是整数、p是整形指针
auto sz = 0, pi = 3, 14; //错误:sz和pi的类型不一致
同样。C++11中的关键字,也有相同的性质 decltype
”如果我们仅仅是想根据初始值确定一个变量合适的数据类型,那么auto是最佳人选。而只有当我们需要推断某个表达式的数据类型,并将其作为一种新的数据类型重复使用(比如,定义多个相同类型变量)或者单独使用(比如,作为函数的返回值类型)时,我们才真正需要用到decltype“。
decltype和auto都可以用来推断类型,但是二者有几处明显的差异:
1.auto忽略顶层const,decltype保留顶层const;
2.对引用操作,auto推断出原有类型,decltype推断出引用;
3.对解引用操作,auto推断出原有类型,decltype推断出引用;
4.auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。
int i=0;
const int ci=i;
auto b=ci; //(1)
int *p=&i;
decltype(*p) c=i;//(2)
以上(1)(2)中变量b,c类型为(int,int&)
auto只能用来标识局部变量的存储类型。auto是默认的存储类型,不需要显示的指定。auto标识的变量存储在栈区中。
extern用来声明在当前文件中引用,但是是在当前项目的其它文件中定义的全局变量***。
如果全局变量未被初始化*,那么将被存在BBS区(静态数据区)中,且在编译时自动将其值赋值为0;
如果已经被初始化,就被存在数据区中。
全局变量,不管是否被初始化,其生命周期是整个程序运行过程。为了节省内存空间,在当前文件中使用extern来声明其它文件中定义的全局变量时,就不会再为其分配内存空间。
声明为register的变量在由内存调入到CPU寄存器后,则常驻在CPU的寄存器中,因此访问register变量将在很大程度上提高效率,因为省去了变量由内存调入到寄存器过程中的好几个指令周期。但编译器可以忽略此选项。
在C语言中的register修饰的变量表示将此变量存储在CPU的寄存器中,由于CPU访问寄存器比访问内存快很多,可以大大提高运算速度。但在使用register时有几点需要注意。
1.用register修饰的变量只能是局部变量,不能是全局变量。CPU的寄存器资源有限,因此不可能让一个变量一直占着CPU寄存器。
2.register变量一定要是CPU可以接受的值。
4.不可以用&运算符对register变量进行取址。
5.register只是请求寄存器变量,不一定能够成功。
参考:https://blog.csdn.net/ccjoe/article/details/44756395
被声明为静态类型的变量,无论是全局的还是局部的,都存储在数据区中,其生命周期为整个程序。静态变量如果没有被初始化,则自动初始化为0,只能够初始化一次。
函数声明为static,除了对该函数声明所在的文件可见外,其他文件都无法访问。
常量区: 存放常量的区间,如字符串常量等,注意在常量区存放的数据一旦初始化后就不能被修改。 程序结束后由系统释放。
char s1[] = “abcdef”;
//1) s1在静态区,"abcdef"无需额外存放,就是数组s1内部,总共占用一个串的内存
const char *p =“abcdef”;
//2)p在静态区,“abcdef”,必须额外存放(在常量区,通常也在静态区),/总共占用一个指针,和一个串的内存
free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容是未定义的。
非静态的全局变量可以用extern扩展到组成源程序的多个文件中,而静态的全局变量的作用域只限于本文件,不能扩展到其它文件。把全局变量改变为静态全局变量后是改变了它的作用域,限制了它的使用范围。
static函数的作用域仅在本文件,在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
源自:https://blog.csdn.net/better_eleven/article/details/90046566
typedef(替换名称)
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
#include
#include
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
int main( )
{
Book book;
strcpy( book.title, "C 教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "编程语言");
book.book_id = 12345;
printf( "书标题 : %s\n", book.title);
printf( "书作者 : %s\n", book.author);
printf( "书类目 : %s\n", book.subject);
printf( "书 ID : %d\n", book.book_id);
return 0;
}
C++保留了一些单词供自己和C++库使用。程序员不应该将保留字用作声明中的标识符。
保留字分为三类:关键字,替代标记和C++库保留名称
以下图片来源:https://www.runoob.com/w3cnote/cpp-keyword-intro.html
(图片来源:C++ Primer Plus)
除关键字外,C++还有一些运算符的字母替代表示,它们被称为替代标记。
(图片来源:C++ Primer Plus)
保留名称是给C++库使用的名称。
如果程序包含了某个头文件,则不应该将该头文件(以及头文件包含的头文件)中定义的宏名用作其他目的。