国嵌一:
什么是数据类型?
数据类型是固定内存大小的别名,是创建变量的模子;
变量的本质?
- 变量是一段实际连续存储空间的别名;
- 程序通过变量来申请并命名存储空间;
- 通过变量的名字可以使用存储空间。
Linux命令行:gcc.test.c 编译 ./a.out 运行
数据类型:
- 自定义类型:typedef定义类型
- 创建变量:TYPE,name
- 通过打印语句证明本质:printf
#include <stdio.h>
int main(void)
{
char c=0;
short s=0;
int i=0;
printf("%d,%d\n",sizeof(char),sizeof(c));
printf("%d,%d\n",sizeof(short),sizeof(s));
printf("%d,%d\n",sizeof(int),sizeof(i));
}
1,1
2,2
4,4
typedef int INT32;
typedef unsigned char BYTE;
typedef struct _demo
{
short s;
BYTE b1;
BYTE b2;
INT32 i;
}DEMO;
int main(void)
{
INT32 i32;
BYTE byte;
DEMO d;
printf("%d,%d\n",sizeof(INT32),sizeof(i32));
printf("%d,%d\n",sizeof(BYTE),sizeof(byte));
printf("%d,%d\n",sizeof(DEMO),sizeof(d));
}
4,4
1,1
8,8
国嵌二:
auto为C语言局部变量的默认属性,编译器默认所有的局部变量都是auto,在栈上分配内存;
register指明将变量存储在寄存器中,只是请求寄存器变量,但是不一定成功;需要的目的是:由于从寄存器中取值比内存中快的多,因此可以应用到讲究性能的实时系统中。特别是函数经常被调用时;register的变量必须是CPU寄存器可以接受的值,不能用&运算符获取变量的地址。
static指明变量的静态属性,同时具有作用域限定符的意义;修饰的局部变量存储在程序的静态工作区;static还可以作为文件作用域标识符,即修饰的全局变量的作用域只是在声明的文件中,修饰的函数作用域只是声明的文件之中。局部变量的生命中周期是函数的运算期,但是static修饰的局部变量是整个生命周期。
补充:
auto 存储类是所有局部变量默认的存储类,auto 只能用在函数内。
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 'extern' 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。当有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
#include <stdio.h>
int main()
{
auto int i = 0;
register int j = 0;
static int k = 0;
return 0;
}
4,4
1,1
8,8
#include <stdio.h>
int g=0;
int m=0;
void f1()
{
int i=0;
i++;
printf("%d,",i); //局部变量,退出后释放
}
void f2()
{
static i=0; //静态局部变量,初始化一次,不释放
i++;
printf("%d,",i);
}
int main()
{
auto int i = 0;
register int j = 0;
static int k = 0;
printf("%0x\n",&i);
//printf("%0x\n",&j); //要求寄存器变量j的地址
printf("%0x\n",&k);
for(i=0;i<5;i++)
{
f1(); //在栈上占用空间,每次运行完就释放
}
printf("\n");
for(i=0;i<5;i++)
{
f2(); //在静态存储区占用空间,只会被初始化一次,每次运行完后保留上一次的值
}
return 0;
}
1,1,1,1,1
1,2,3,4,5
- //auto int g=0;//错误,文件作用域中g的声明指定了auto
- //register int m=0;//m的寄存器名无效,全局变量不可能是寄存器变量
另一个文件如果是int test_g=1;则可以打印test_g得值;
int test_f1()
{
return test_fun();
}
如果是static int test_g=1;则不可以打印test_g得值
static int test_g=1;
int test_fun()
{
return test_g;
}
如果是static int test_fun()则不可以打印test_g得值,如果再加一个函数,可以打印test_g得值。
- 外部变量和静态变量由编译程序给予隐含的初始值0;
- 局部变量的初始化每进入函数便初始化一次。
- 外部或静态变量仅在编译时初始化一次。
- 自动变量或寄存器变量只能显示初始化,否则将有不确定的值。
- 外部数据的说明,如果带有初始化项,则当成一个定义。
auto 普通局部栈变量,是自动存储,这种对象会自动创建和销毁 ,建议这个变量要放在堆栈上面,调用函数时分配内存,函数结束时释放内存。一般隐藏auto默认为自动存储类别。我们程序都变量大多是自动变量。
#include <stdio.h>
int main(void)
{
auto int i = 9; /* 声明局部变量的关键字是 auto; 因可以省略, 几乎没人使用 */
printf("%d\n", i);
getchar();
return 0;
}
Register变量:动态和静态变量都是存放在内存中,程序中遇到该值时用控制器发指令将变量的值送到运算器中,需要存数再保存到内存中。如果频繁使用一个变量,比如一个函数体内的多次循环每次都引用该局部变量,我们则可以把局部变量的值放到CPU的寄存器中,叫寄存器变量。不需要多次到内存中存取提高效率。但是只能局部自动变量和形参可以做寄存器变量。在函数调用时占用一些寄存器,函数结束时释放。不同系统对register要求也不一样,比如对定义register变量个数,数据类型等限制,有的默认为自动变量处理。所以在程序一般也不用。
#include <stdio.h>
#include <>
#define TIME 1000000000
int m, n = TIME; /* 全局变量 */
int main(void)
{
time_t start, stop;
register int a, b = TIME; /* 寄存器变量 */
int x, y = TIME; /* 一般变量 */
time(&start);
for (a = 0; a b; a++);
time(&stop);
printf("寄存器变量用时: %ld 秒\n", stop - start);
time(&start);
for (x = 0; x y; x++);
time(&stop);
printf("一般变量用时: %ld 秒\n", stop - start);
time(&start);
for (m = 0; m n; m++);
time(&stop);
printf("全局变量用时: %ld 秒\n", stop - start);
return 0;
}
输出结果:寄存器变量用时: 1 秒 一般变量用时: 8 秒 全局变量用时: 9 秒
static 可以用来修饰局部变量,全局变量以及函数。在不同的情况下 static 的作用不尽相同。
(1)修饰局部变量
#include void fun()
{
static int a=1;
a++;
printf("%d\n",a);
}
int main(void)
{
fun();
fun();
return 0;
}
程序执行结果为: 2 3
(2)修饰全局变量
//有file1.c
int a=1;
file2.c
#include <stdio.h>
extern int a;
int main(void)
{
printf("%d\",a);
return 0;
}
则执行结果为 1
但是如果在 file1.c 中把 int a=1 改为 static int a=1;那么在file2.c是无法访问到变量a的。原因在于用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。
(3)修饰函数
用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。
国嵌三:
if语句需要注意:
- bool型变量应该直接出现在条件中,不能进行比较;
- 普通变量和0值比较时,0值应该出现在比较符号的左边,防止出现赋值的情况;
- float型变量不能直接进行0值比较,需要定义精度;
在输入时:使用非零值表示真;零值表示假。在输出时:真的结果是1,假的结果是0。当在一个需要布尔值的地方,也就是其它类型转化为布尔类型时,比如 if 条件判断中的的条件;“输出”的意思是:程序的逻辑表达式返回的结果,也就是布尔类型转化为其他类型时,比如 a==b的返回结果,只有0和1两种可能。
switch语句需要注意:
- switch语句对应单个条件多个分值的情况,
- 每个case语句必须要有break,否则会出现分支重叠;
- case语句中的值只能是整形或者字符型。
if语句可以安全从功能上替代switch语句,但是switch语句无法替代if语句。
void f1(int i)
{
if( i < 6 )
{
printf("Failed!\n");
}
else if((6<i)&&(i<=8))//范围的话switch无法替代if
{
printf("Good!\n");
}
else
{
printf("Perfect!\n");
}
}
void f2(char i)
{
switch(i)
{
case 'c':
printf("Compile\n");
break;
case 'd':
printf("Debug\n");
break;
case 'o':
printf("Object\n");
break;
case 'r':
printf("Run\n");
break;
default:
printf("Unknown\n");
break;
}
}
int main()
{
f1(5);
f1(9);
f2('o');
f2('d');
f2('e');
}
failed prefect object debug unknown
循环语句分析:
- do语句先执行后判断,循环体至少执行一次;
- while语句先判断后执行,循环体可能不执行;
- for语句先判断后执行,相比于while更简便;
#include <stdo.h>
int f1(int n)
{
int ret = 0;
int i = 0;
for(i=1; i<=n;i++)
{
ret += i;
}
return ret;
}
int f2(int n)
{
int ret = 0;
while( (n > 0) && (ret += n--) );
return ret;
}
int f3(int n)
{
int ret = 0;
if( n > 0 )
{
do//至少执行一次
{
ret += n--;
}while( n );
}
return ret;
}
int main()
{
printf("%d\n", f1(10));
printf("%d\n", f2(10));
printf("%d\n", f3(10));
}
55 55 55
- break语句是跳出循环;
- continue语句是跳出本次循环,进入下次循环;
- switch语句不能使用continue,因为switch语句本身不存在循环。
#include <stdio.h>
int func(int n)
{
int i = 0;
int ret = 0;
int* p = (int*)malloc(sizeof(int) * n);//分配动态内存
do
{
if( NULL == p )
break; //不能用return 0;如果返回值不是0,会出现内存错误
if( n < 0 )
break; //不能用return 0;如果返回值不是0,会出现内存错误
for(i=0; i<n;i++)
{
p[i] = i;
printf("%d,", p[i]);
}
ret = 1;
}while(0);
free(p); //释放内存
return ret;
}
int main()
{
if( func(10) )
{
printf("OK");
}
else
{
printf("ERROR");
}
}
0,1,2,3,4,5,6,7,8,9 OK
国嵌四:
goto禁用。
goto虽好不使用,因为它使得程序的控制流难以跟踪,使程序难以理解和难以修改。:
#include <stdio.h>
void func(int n)
{
int* p = NULL;
if( n < 0 )
{
goto STATUS;
}
p = malloc(sizeof(int) * n);
STATUS:
p[0] = n;
}
int main()
{
f(1);
f(-1);
return 0;
}
void的意义:
C语言没有定义void究竟是多大的内存的别名,无法在内存中裁剪出void对应的变量。如果函数没有返回值,没有参数,就应该声明为void型;且在C语言中void不占据内存。
void*指针应该注意的问题:
- 在C语言中只有相同类型的指针才可以进行相互赋值;
- void*指针作为左值用于接收任意类型的指针;
- void*作为右值赋值给其他指针,需要强制类型的转换。
void* my_memset(void* p,char v,int size)
{
void* ret=p;
char* dest=(char*)p;
int i=0;
for(i=0;i<size;i++)
{
dest[i]=v;
}
return 0;
}
int main()
{
int a[5]={1,2,3,4,5};
int i=0;
for(i=0;i<5;i++)
{
printf("%d,",a[i]);
}
printf("\n");
my_memset(a,0,sizeof(a));
for(i=0;i<5;i++)
{
printf("%d,",a[i]);
}
return 0;
}
1,2,3,4,5
0,0,0,0,0
extern 中隐藏的意义:
- extern用于声明外部定义的变量和函数,
- extern告诉编译器用C语言的方式编译。
(由于c++编译器和一些其他编译器会按照自己的编译方式运行,而通过extern可以命令编译器以标准的C语言方式进行编译)
test.c
/*
extern "C"
{
int add(int a, int b)
{
return a + b;
}
}
*/
extern int g;
extern int get_min(int a, int b);
int main()
{
printf("%d\n",g);
printf("%d\n",get_min(1,5));
return 0;
}
// test2.c
int g = 100;
int get_min(int a, int b)
{
return (a < b) ? a : b;
}
gcc test.c test2.c ./a.out
100,1
sizeof不是函数:
- sizeof是编译器的内置指示符,不是函数,
- sizeof用于计算相应实体所占据的内存大小;
- sizeof的值在编译期就已经确定了
int main()
{
int a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);//充分证明sizeof不是函数
printf("%d\n", sizeof(int));
return 0;
}
4,4,4
国嵌五:
const修饰变量:
- const修饰的变量是只读的,其本质还是变量,
- const会占据一定的内存空间;
- 本质上const只对编译器有用,在运行时无用;
int main(void)
{
const int cc=1;
printf("%d\n",cc);
cc=3; //只读类型,不能修改,故报错
printf("%d\n",cc);
}
int main(void)
{
const int cc=1;
int *p=(int *)&cc;
printf("%d\n",cc);
*p=3;
printf("%d\n",cc);
}
1,3
const修饰数组:
- const修饰的数组是只读的;
- const修饰的数组空间不可被改变,通过指针可以更改。
const修饰指针:(左数右指)
- 当const出现在*左边的时候,指针指向的数据为常量,
- 当const出现在*右边的时候,指针本身为常量。
- const int *p; //p可变,p指向的内容不可变;
- int const *p; //p可变,p指向的内容不可变;
- int *const p; //p不可变,p指向的内容可变;
- const int *const p; //p和p指向的内容都不可变;
#include <stdio.h>
int main(void)
{
int i=0;
//const int *p=&i;
//*p=3; //只读,不能改变,报错
//int const*p=&i;
//*p=3; //同上
int* const p=&i;
*p=3; //编译通过
//int* const p=&i;
//p=NULL; //只读,不能改变,报错
//const int* const p=&i;
//*p=3; //同上
//p=NULL; //同上
}
const修饰函数参数和返回值:
- const修饰函数表示在函数体内不希望改变参数的值。
- const修饰函数返回值表示返回值不可改变,多用于返回指针的情形。
#include <stdio.h>
const int* func()
{
static int count=0;
count++;
return &count;
}
int main(void)
{
int i=0;
const int* p=func();//没有const就会有警告,因为返回的地址是不可改变的
printf("%d\n",*p);
}
1
volatile的理解:
- volatile可以理解为编译器警告指示字,
- volatile用于告诉编译器必须每次去内存中取变量的值;
- volatile主要修饰被多个线程访问的变量,
- volatile也可以修饰可能被未知因数更改的变量。
const和volatile是可以同时修饰一个变量的 const只是表示变量只读 不能出现在赋值号左边 防止程序“意外”修改 并且编译器一定会做优化 不会每次去内存取值 这个时候如果外部事件 如中断服务程序 改了这个变量的内存值 那么由于编译器优化就不会出有反应 这样会导致错误 加上volatile就告诉编译器 不要做任何优化 并且每次都去内存取值 而且这个变量不可以当左值使用 .
问题1:const与volatile是否可以修饰同一个变量?
如果一个变量不会被本程序改变,通常可能给它加上const,但如果该变量可能被其他程序改变而本程序又在检测这个变量的值,就需要给它加上volatile,于是变量就同时有volatile和const了,这个时候i具有const和volatile的双重属性。
问题2:const volatile int i=0;这个是时候i具有什么属性?编译器如何处理这个变量?
i变量不可以在编译过程中被程序代码修改,同时编译器不得对i进行优化编译。