双层for循环
情况一:两层for循环,外层循环次数为1000000,内层循环次数为10。
情况二:两层for循环,内层循环次数为1000000,外层循环次数为10。
正常情况下,次数多的循环放在内层,会减少cache更新数据的次数,当离开内层循环的时候会恢复外层的计数器,外层循环次数越少,恢复计数器的次数越少,效率较高
例外
// 1
for(i=0;i<100000;i++)
for(j=0;j<10;j++)
a[i,j]=0;
//2
for(i=0;i<10;i++)
for(j=0;j<100000;j++)
a[j,i]=0;
a为i行j列的数组,读取数组时,还要考虑到数组的存储方式,按行读是顺序存储
因为cpu从内存中获取到数据存放到cache的时候,是一块一块获取的,如果cache的利用率高,那么效率肯定更高。因为第一个代码对数组a的访问是顺序的,所以换页次数更少,时间开销更小。
分析>> 1和/2的区别
效率不同
因为>> 是右移符百号,它在操作时只允许整数
/是除法,它可以操作不同类型的数据:浮点数除法最终结果是浮点数,整数除法的最终结果是整数。
右移操作通常情况下,会比整数除法速度快。涉及容到浮点数的除法速度是最慢的。
结果可能不同
n为非负数时,>> 1和/ 2的结果是一样的
n为负数且还是偶数时,>> 1和/ 2的结果是一样的
n为负数且还是奇数时,>> 1和/ 2的结果是不一样的
原因是奇数除二会发生截断现象。而>> 1和/ 2在n为负奇数时截断的反向不一样。
-5 / 2 = -(int)2.5 = -2,这里是把绝对值变小了,加个负号,结果就变大了。
-5 >> 1 = (1011) >> 1 = (1101) = -3,假设用4-bit表示一个整数,补码表示。发现结果变小了。
函数返回局部变量/局部指针
局部变量
局部变量分局部自动变量和局部静态变量,由于c返回的是值,因此返回一个局部变量是可以的,无论自动还是静态,因为这时候返回的是这个局部变量的值。另外,函数返回局部变量时实际上是返回变量值的拷贝。a为局部变量,在栈区存储,虽然在函数调用结束后所在内存会被释放回收掉,但返回值不是访问地址,而是a的拷贝副本。
int INTtest(){
int a = 10;
return a;
}
局部指针
局部指针跟上面所述的局部变量一样。可以返回一个局部指针的值,也可以返回一个局部静态指针的地址,但不应该返回一个局部自动指针的地址,除非自动指针的地址指向数据区或堆区。
返回值为局部指针,可以分为:(1)声明局部变量,返回其地址;(2)声明局部数组,返回数组名;(3)声明局部指针,返回该指针。
1.返回局部变量地址,局部变量分为自动和静态局部变量,不应该返回指向局部自动变量的指针,因为函数调用结束后栈上声明的局部自动变量被抛弃,这个指针指向一个不再存在的对象,是无意义的。但可以返回指向局部静态变量的指针,因为静态变量存在数据区,它的生存期从定义起到程序结束。
2.返回数组名,与局部变量相同(自动和静态),调用的结果指向栈上声明的数组的首地址,函数结束后自动数组内存释放掉,将无法对其进行访问(某些编译器Release下可以访问,但理论上是没有意义的),但静态数组可以。
3.返回指针,调用结果指向该指针指向的内存,在函数结束后在栈上声明的指针也会被释放掉,但应该注意原指针指向的内存地址,若改地址同样是栈上声明的,则无法访问,如果是数据区或堆区的内容,则可以访问。
1和2比较容易理解,这里不再进行举例,仅对第3种情况的数据区和堆区进行说明。
数据区:返回指针,但指针指向的内存地址存储在数据区(常量区)
char * test(){
char *arr = "hello world"; //字符常量
//static char arr[] = "hello world!";
return arr;
}
void test_(){
char *s = test();
printf("s = %s\n", s);
}
int main(){
test_();
return 0;
}
堆区:返回指针,指针指向的内存地址存储在堆区,没有手动释放前都可以访问到。
malloc()
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。如果希望在分配内存的同时进行初始化,请使用 calloc() 函数。
char *getstring(){
char *p;
p = malloc(100);
memset(p, 0, 100);
strcpy(p, "hello");
return p;
}
void test(){
char *ret = getstring();
printf("%s\n", ret);
free(ret);
ret = NULL;
}
int main(){
test();
system("pause");
return 0;
}
如果指针指向的内存是在函数内部申请的,基本不干返回指针这种事,做这种事情很有可能是在给自己找麻烦,多数做的都是在可以释放的地方申请好内存,通过指针传递进函数,申请和释放都在同一函数中。上面的函数,可以修改如下:
void getstring01(char *p){
strcpy(p, "hello");
}
void test01(){
char *p = NULL;
p = malloc(100);
memset(p, 0, 100);
//char *ret = NULL;
getstring01(p);
printf("%s\n", p);
free(p);
p = NULL;
}
int main(){
test01();
return 0;
}
const和指针
有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定。
由于常量一旦被创建后其值就不能再改变,所以常量必须在定义的同时赋值(初始化),后面的任何赋值行为都将引发错误。
#include <stdio.h>
int getNum(){
return 100;
}
int main(){
int n = 90;
const int MaxNum1 = getNum(); //运行时初始化
const int MaxNum2 = n; //运行时初始化
const int MaxNum3 = 80; //编译时初始化
printf("%d, %d, %d\n", MaxNum1, MaxNum2, MaxNum3);
#100, 90, 80
return 0;
}
const 和指针
const 也可以和指针变量一起使用,这样可以限制指针变量本身,也可以限制指针指向的数据。const 和指针一起使用会有几种不同的顺序,如下所示:
const int *p1;
int const *p2;
int * const p3;
在最后一种情况下,指针是只读的,也就是 p3 本身的值不能被修改;在前面两种情况下,指针所指向的数据是只读的,也就是 p1、p2 本身的值可以修改(指向不同的数据),但它们指向的数据不能被修改。