1.结构体内存对齐:
#include<stdio.h>
struct a{
int a;
int b;
char c;
double d;
}aa;
int main()
{
printf("%d ",sizeof(aa.a));
printf("%d ",sizeof(aa.d));
printf("%d ",sizeof(aa.c));
printf("%d ",sizeof(aa.d));
printf("%d",sizeof(aa));
return 0;
}
程序输出为:
4 4 1 8 24
其中32位操作系统:
char : 1 int :4 short : 2 unsigned int : 4 long : 4 unsigned long : 4 long long : 8 float : 4 double : 8 指针 : 4
64位操作系统
char : 1 int :4 short : 2 unsigned int : 4 long : 8 unsigned long : 8 long long : 8 float : 4 double : 8 指针 : 8
内存对齐:
每个参数按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍。不够就补空字节。
内存对齐主要遵循下面三个原则:
1.结构体变量的起始地址能够被其字节最长的参数大小整除
2.结构体每个参数相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个参数后面补充字节
3.结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
4. 如果嵌套了结构体的情况,被嵌套的结构体对齐到其自身对齐数的整数倍处(结构体的对齐数就是其内部成员中最大的对齐数),此时结构体的整体大小就是所有最大对齐数(含被嵌套结构体的对齐数)的整数倍。
第一个是int 型四个字节,占0x0000-0x0003,第二个int型占0x0004-0x0007,第三个char型占0x0008-0x0009第四个double型8字节,为满足原则2,应占0x0017-0x0024,所以char补齐7个字节。且24%8==0满足原则三,
即int(4)+int(4)+char(1)+补齐(7)+double(8)=24
当b和d换位结果相同。
再来一个例子
struct S1
{
char c1;
short c2;
int i;
}S;
struct S2
{
char c1;
struct S;
double d;
};
struct s1长度char(1)+short(2)+补齐(1)+int(4)=8
struct s2长度char(1)补齐(3)+struct(8)+补齐(4)+double(8)=24
2.头文件的使用和指针使用字符串
#include<stdio.h>
int main()
{
char s[] = "Hello,Xiyounet!";
char *s2;
strcpy(s2,s);
printf("%s",s2);
return 0;
}
改为:
#include<stdio.h>
#include<string.h>
int main()
{
char s[] = "Hello,Xiyounet!";
char *s2;
char *ss=s;
strcpy(s2,ss);
printf("%s",s2);
return 0;
}
3.函数的声明和参数存在的范围
如下程序
int main()
{
f();
return 0;
}
char *f()
{
char s[] = "hello,world!";
return s;
}
在使用函数应先在main函数前进行声明,定义它的类型。
在函数使用中不能返回函数内部定义的值,如要使用需加入static全局变量。
4.函数的调用
void sum(int a,int b){
printf("%d", a+b);
}
int main()
{
int a=1,b=2;
void sum(a,b);
return 0;
}
在使用函数时不用void。
5.指针不可修改
char * s="Hello,Xiyounet!";
printf("%s",s);
s[0]='h';
printf("%s",s);
可修改为
#include<stdio.h>
int main()
{
char sz[]="Hello,Xiyounet!";
printf("%s",sz);
char *p=sz;
*p='h';//p[0]='h';
printf("%s",p);
}
输出为:Hello,Xiyounet!hello,Xiyounet!
6.main函数的参数是什么?如何给main函数传递参数?**
(1)通过命令参数传递参数;
习惯上这两个参数写为argc(argument count)和argv(argument vector)
argc是个整形变量,命令行参数个数,
char *argv[]是字符指针数组,
argv[0] 为程序运行的全路径名
argv[1] 为在DOS命令行中执行程序名后的第一个字符串;
argv[2] 为执行程序名后的第二个字符串;
argc:argc是执行程序时的命令行参数个数。需要注意的是,程序本身的文件也算一个。例如你在命令参数里写了2个参数,那argc等于3。
argv[]:存的是命令行参数(字符串)的首地址,包括程序本身的文件和尾部的dull。
在项目——》属性——》配置属性——》调试——》命令参数,设置命令参数就可以传入参数
int main(int argc, char *argv[], char *envp[])
{
int i = 0;
for (i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
输出值即为你设置的参数。
envp里存放正是系统的环境变量,可以右键单击计算机——》属性——》高级系统设置——》环境变量,打开环境变量设置窗口
#include<stdio.h>
#include<Windows.h>
int main(int argc, char* argv[], char* envp[])
{
int i = 0;
while (envp[i] != NULL)
{
printf("%s\n", envp[i]);
i++;
}
system("pause");
return 0;
}
7.c和.cpp文件是如何变成可执行文件的?**
一.预编译:
- .处理源代码文件中的“#”开始的预编译指令。如“#include”、“#define”等。具体如下:
- 宏替换:将所有的“#define”删除,并展开所有的宏定义。 处理条件预编译指令:如“#if" “#endif” “#elif” "#else"指令:
- 处理“#include”预编译指令,将被包含的头文件插入到预编译指令的位置。(递归插入,被包含的文件还可能包含其他文件)
- 删除所有注释(“//”、“/* */”)。 添加行号和文件标识(以便编译时期,出错显示行号、调试使用行号信息)
- 保留所有#pragma编译器指令。
二.编译:
- 词法分析:源代码经过扫描器,将源代码的字符序列分割成一系列的记号(关键字、标识符、字面量(数字、字符串)、特殊符号(如“+”“-”)),同时将标识符存放到符号表,数字、字符串常量放到文字表等工作,以备后续使用。
- 语法分析:语法分析器对扫描器产生的记号进行语法分析,生成语法树(以表达式为节点的树,检验表达式的逻辑性)。
- 语义分析:对静态语义(编译期可以确定的语义,包括声明、类型匹配、类型转换等)进行分析。如浮点型表达式赋给整型表达式时,类型不匹配,语义分析隐含执行了浮点型到整型转换的过程。
- 代码优化:源代码级优化器(优化语法树,如替换重复或简单的表达式)进行优化。
三.汇编:汇编器将汇编代码变成机器可以执行的指令
四.链接:空间与地址分配,符号解析与重定位。
8.范围
unsigned int 的范围为0~65535//32767+32768
unsigned char的范围为0~255
9.左移运算符, 右移运算符,取反规则
printf("%d",~9);
printf("%d",(-9)>>1);
printf("%d",(-9)<<1);
-9二进制为原码10001001补码为01110111
左移运算符左移补零,10010010,即为-18
右移运算符因为负数右移补一,10000101,即为-5
取反~1 = 0, ~0 = 1
~(01110111)=10001000即为8
10.&&和&
int i=0;
int j=0;
int ii = i++ && ++j;
printf("%d %d %d\n", ii, i, j);
i = 0;
j = 0;
ii = ++i & j++;
printf("%d %d %d\n", ii, i, j);
输出:
010
011
原理:
1.&&
只要左端条件式为假直接不成立,不会去判断右端条件式。
相同点:只要有一端为假,则语句不成立,即ii = i++ && ++j;当i=0时不在执行j的命令所以j输出为0。
2.&
&左右两端条件式有一个为假就会不成立,但是两端都会运行,比如 ii = ++i & j++;i=0即使为假,也会去判断是否成立。即j自增。
另:strlen只计算\0之前的字符。