一些收藏的内容!不断更新。。。
************************************************小小战士,战士中的战斗机******************************************************************************如果哪里有什么错误的地方,还请大家提出你宝贵的意见和建议,欢迎批评指正,共同进步!*****************************
**********************************************************************************************************************************
******************************************************轻轻的我走了****************************************************************
*****************************************************正如我轻轻地来***************************************************************
******************************************************我挥一挥衣袖****************************************************************
******************************************************带走一丝感悟****************************************************************
**********************************************************************************************************************************
关于算法方面
----------------------------------------------------------------------------------------------------------------------------------
友情链接:http://bbs.bccn.net/thread-389340-1-1.html
下面是第一个简单的求素数的算法:
#include<stdio.h>
//素数为只能被1和本身整除的数
//这是一般的求素数的方法,缺点是如果数太大,就很耗时很耗内存,执行效率会很低很低
int main()
{
int i,j,n=0,count=0,num=1000000;
printf("1~%d内的素数:\n",num);
for(i=1;i<=num;i++)
{
for(j=1;j<=i;j++)
if(i%j==0) count++;
if(count<=2)
{
printf("%-d ",i);
n++;
if(n%10==0) printf("\n");
}
count=0;
}
printf("\n");
return 0;
}
下面是第二个求素数的算法:
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>
//计算MAX以内的素数,如果MAX很大则计算量很大,花费时长很长,短则几秒几分钟,长则几天几月甚至几年,存放数据的文件也会很大甚至连打开都成了一件
//头疼的事情,但这比起十几行代码的简单计算素数的方法,在运行速度和思维方式上还是有很大的优势和值得借鉴的地方的,也具有一定的学习价值的!
//至少执行效率上和内存利用效率上会提高几十上百倍上千倍
#define MAX 200000000//5分钟左右
#define N MAX/32+1//数组大小
unsigned int A[N];//数组的每一位代表一个数
int main()
{
clock_t start,finish;
start=clock();
int i,k,n=N,max=MAX,size=(int)sqrt(max),x,y;
memset(A,0,sizeof(A));
for (i=2; i<=size; i++)//循环遍历sqrt(max)之内的因子
if (!(A[(i-1)/32]&(1<<((i-1)%32))))//位为0
{
k=i+i;//2倍
while (k<=max)
{
A[(k-1)/32]|=(1<<((k-1)%32));//标记1
k+=i;//3,4,5,...倍
}
}
FILE *f=fopen("prime.txt","w");//将所有素数导出到文件
for (i=2; i<=max; i++)
if (!(A[(i-1)/32]&(1<<((i-1)%32)))) fprintf(f,"%d\n",i);
fclose(f);
finish=clock();
printf("%.3lf\n",((double)finish-start)/1000);//打印程序执行的时间
//其实程序到这里就应该完了,但如果想查看每位的标记情况,就有了下面的代码
printf("A[0]: ");//1不算在内
for (i=2; i<=max; i++)
{
printf("%-3d",A[(i-1)/32]>>((i-1)%32)&1);
if(i%32==0) printf("\n\nA[%d]:",i/32);
}
printf("\n");
return 0;
}
下面是第三个求素数的算法:
这个程序也是用的筛选法,不过他是通过每次筛一段达到节省空间的目的,同时不用开几千万的数组使得时间也降下来了
首先预处理出sqrt(max)内所有的素数,由上面我提到的结论,只需要筛这些素数就可以了
然后第一次筛选1-NUMP内的数,第二次筛选NUMP+1-NUMP*2内的数,......一次类推
每次筛选的时候记录下上面的素数加到了哪里,比如筛选3的时候,NUMP=100,肯定会加到102,这时候由于102>100,所以中断,记录下102-100=2,这样下次筛选3的时候从2开始就好了
// 准筛法求一万亿以内素数(VC 6.0)
// 耗时13137.3秒(Dell机)
#include <stdio.h>
#include <time.h> //用于统计时间的头文件
#include <stdlib.h>
#include <memory.h>
#define PMAX 2000000000 //原著这里是100亿 我给改成20亿了 100亿要耗时好几个小时呢
#define NUMP 78500
long prime [NUMP]={2, 3};
long prime2[NUMP]={4, 6};
void set_Prime_table(void)
{
long n=5,p2=9, k,kr=1,kp=2;
while(1)
{
if(n==p2)
{
kr++;
p2=prime[kr]*prime[kr];
continue;
}
for(k=1; k<kr; k++)
{
if(n % prime[k]==0)goto nadd2;
}
prime[kp]=n;
prime2[kp]=n+n;
if(++kp==NUMP)break;
nadd2:
n=n+2;
}
if((__int64)n*n<PMAX)abort( );
return;
}
int main(void)
{
#define BLOCK 0x8000 //VC6.0最佳值(Dell机), 在赛扬-333上0x4000为最佳
static char pp[BLOCK];
__int64 BASE,np=0;
long block,dn,k,nn,t2,t1=clock( ); //t1、t2分别记录开始和结束时间
set_Prime_table( );
for(np=BASE=0;BASE<PMAX;BASE+=BLOCK)
{
memset(pp,1,BLOCK); //预设都是素数
if(BASE==0)pp[0]=pp[1]=0; //0和1不是素数
block = BLOCK;
if(PMAX-BASE<BLOCK)block=(long)(PMAX-BASE);
for(k=0; k<NUMP; k++)
{
dn = prime [k];
nn = prime2[k];
while(nn<block)
{
pp[nn]=0;
nn=nn+dn;
}
prime2[k]=nn-BLOCK;
}
for(k=0; k<block; k++)if(pp[k])
{
if(BASE>=PMAX-BLOCK && k>block-1000)
printf("%I64d\t",k+BASE);//显示最后1000个连续数中的素数
np++;
}
}
t2 = clock( );
printf("\n%0.1f sec\nTotal: %I64d\n",(t2-t1)/1E3,np);
return 0;
}
下面是第四个求素数的算法:
速度上是没法超越了,筛选法仍然是我所知道的最快的素数算法。
这里介绍另一种素数判定方法,原理是利用费马小定理加二次探测定理来判定素数的一种概率算法。
关于速度,要看你怎么定义了,如果要找出一个范围内的所有素数,它的效率是不如素数筛的;但如果只判定某一个数是否是素数,它的优势非常明显,时间复杂度可以任为是O(1)。(这段代码中实际是O(log(n)),出于简化的目的,isprime不判断4以下数)。
而空间复杂度上筛法无法与这种算法相比。这也是筛法的软肋,筛法本就是一种以空间换时间的算法。
这段代码计算1千万以内的素数需要大约16秒,可以估算,计算20亿以内大约需要近1个小时。
呵呵,算是开拓一下大家的思路吧。
程序代码:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int isprime(int n)
{
int ps[32], a, p, x, r, t, i, j;
for(t = -1, p = n - 1; p; p >>= 1) ps[++t] = p & 1;
for(i = 0; i < 16; i++) //测试16次
{
a = (rand() % (n - 2)) + 2;
for(r = 1, j = t; j >= 0; j--)
{
x = r;
r = ((long long)x * x) % n;
if(r == 1 && x != 1 && x != n - 1) return 0;
if(ps[j]) r = (long long)r * a % n;
}
if(r != 1) return 0;
}
return 1;
}
int main()
{
int i, c, t0;
srand(time(NULL));
t0 = clock();
for(c = 2, i = 5; i <= 10000000; i += 2)
c += isprime(i);
printf("count = %d\nuse time %.3f s\n", c, (double)(clock() - t0) / 1000);
return 0;
}
----------------------------------------------------------------------------------------------------------------------------------
素数与素性测试
友情链接:http://www.
一个数是素数(也叫质数),当且仅当它的约数只有两个——1和它本身。规定这两个约数不能相同,因此1不是素数。对素数的研究属于数论范畴,你可以看到许多数学家没事就想出一些符合某种性质的素数并称它为某某某素数。整个数论几乎就围绕着整除和素数之类的词转过去转过来。对于写代码的人来说,素数比想像中的更重要,Google一下BigPrime或者big_prime你总会发现大堆大堆用到了素数常量的程序代码。平时没事时可以记一些素数下来以备急用。我会选一些好记的素数,比如4567, 124567, 3214567, 23456789, 55566677, 1234567894987654321, 11111111111111111111111 (23个1)。我的手机号前10位是个素数。我的网站域名的ASCII码连起来(77 97 116 114 105 120 54 55 46 99 111 109)也是个素数。还有,我的某个MM的八位生日也是一个素数。每次写Hash函数之类的东西需要一个BigPrime常量时我就取她的生日,希望她能给我带来好运。偶尔我叫她素MM,没人知道是啥意思,她自己也不知道。
素数有很多神奇的性质。我写5个在下面供大家欣赏。
1. 素数的个数无限多(不存在最大的素数)
证明:反证法,假设存在最大的素数P,那么我们可以构造一个新的数2 * 3 * 5 * 7 * ... * P + 1(所有的素数乘起来加1)。显然这个数不能被任一素数整除(所有素数除它都余1),这说明我们找到了一个更大的素数。
2. 存在任意长的一段连续数,其中的所有数都是合数(相邻素数之间的间隔任意大)
证明:当0<a<=n时,n!+a能被a整除。长度为n-1的数列n!+2, n!+3, n!+4, ..., n!+n中,所有的数都是合数。这个结论对所有大于1的整数n都成立,而n可以取到任意大。
3. 所有大于2的素数都可以唯一地表示成两个平方数之差。
证明:大于2的素数都是奇数。假设这个数是2n+1。由于(n+1)^2=n^2+2n+1,(n+1)^2和n^2就是我们要找的两个平方数。下面证明这个方案是唯一的。如果素数p能表示成a^2-b^2,则p=a^2-b^2=(a+b)(a-b)。由于p是素数,那么只可能a+b=p且a-b=1,这给出了a和b的唯一解。
4. 当n为大于2的整数时,2^n+1和2^n-1两个数中,如果其中一个数是素数,那么另一个数一定是合数。
证明:2^n不能被3整除。如果它被3除余1,那么2^n-1就能被3整除;如果被3除余2,那么2^n+1就能被3整除。总之,2^n+1和2^n-1中至少有一个是合数。
5. 如果p是素数,a是小于p的正整数,那么a^(p-1) mod p = 1。
这个证明就有点麻烦了。
首先我们证明这样一个结论:如果p是一个素数的话,那么对任意一个小于p的正整数a,a, 2a, 3a, ..., (p-1)a除以p的余数正好是一个1到p-1的排列。例如,5是素数,3, 6, 9, 12除以5的余数分别为3, 1, 4, 2,正好就是1到4这四个数。
反证法,假如结论不成立的话,那么就是说有两个小于p的正整数m和n使得na和ma除以p的余数相同。不妨假设n>m,则p可以整除a(n-m)。但p是素数,那么a和n-m中至少有一个含有因子p。这显然是不可能的,因为a和n-m都比p小。
用同余式表述,我们证明了:
(p-1)! ≡ a * 2a * 3a * ... * (p-1)a (mod p)
也即:
(p-1)! ≡ (p-1)! * a^(p-1) (mod p)
两边同时除以(p-1)!,就得到了我们的最终结论:
1 ≡ a^(p-1) (mod p)
可惜最后这个定理最初不是我证明的。这是大数学家Fermat证明的,叫做Fermat小定理(Fermat's Little Theorem)。Euler对这个定理进行了推广,叫做Euler定理。Euler一生的定理太多了,为了和其它的“Euler定理”区别开来,有些地方叫做Fermat小定理的Euler推广。Euler定理中需要用一个函数f(m),它表示小于m的正整数中有多少个数和m互素(两个数只有公约数1称为互素)。为了方便,我们通常用记号φ(m)来表示这个函数(称作Euler函数)。Euler指出,如果a和m互素,那么a^φ(m) ≡ 1 (mod m)。可以看到,当m为素数时,φ(m)就等于m-1(所有小于m的正整数都与m互素),因此它是Fermat小定理的推广。定理的证明和Fermat小定理几乎相同,只是要考虑的式子变成了所有与m互素的数的乘积:m_1 * m_2 ... m_φ(m) ≡ (a * m_1)(a * m_2) ... (a * m_φ(m)) (mod m)。我为什么要顺便说一下Euler定理呢?因为下面一句话可以增加我网站的PV:这个定理出现在了The Hundred Greatest Theorems里。
谈到Fermat小定理,数学历史上有很多误解。很长一段时间里,人们都认为Fermat小定理的逆命题是正确的,并且有人亲自验证了a=2, p<300的所有情况。国外甚至流传着一种说法,认为中国在孔子时代就证明了这样的定理:如果n整除2^(n-1)-1,则n就是素数。后来某个英国学者进行考证后才发现那是因为他们翻译中国古文时出了错。1819年有人发现了Fermat小定理逆命题的第一个反例:虽然2的340次方除以341余1,但341=11*31。后来,人们又发现了561, 645, 1105等数都表明a=2时Fermat小定理的逆命题不成立。虽然这样的数不多,但不能忽视它们的存在。于是,人们把所有能整除2^(n-1)-1的合数n叫做伪素数(pseudoprime),意思就是告诉人们这个素数是假的。
不满足2^(n-1) mod n = 1的n一定不是素数;如果满足的话则多半是素数。这样,一个比试除法效率更高的素性判断方法出现了:制作一张伪素数表,记录某个范围内的所有伪素数,那么所有满足2^(n-1) mod n = 1且不在伪素数表中的n就是素数。之所以这种方法更快,是因为我们可以使用二分法快速计算2^(n-1) mod n 的值,这在计算机的帮助下变得非常容易;在计算机中也可以用二分查找有序数列、Hash表开散列、构建Trie树等方法使得查找伪素数表效率更高。
有人自然会关心这样一个问题:伪素数的个数到底有多少?换句话说,如果我只计算2^(n-1) mod n的值,事先不准备伪素数表,那么素性判断出错的概率有多少?研究这个问题是很有价值的,毕竟我们是OIer,不可能背一个长度上千的常量数组带上考场。统计表明,在前10亿个自然数中共有50847534个素数,而满足2^(n-1) mod n = 1的合数n有5597个。这样算下来,算法出错的可能性约为0.00011。这个概率太高了,如果想免去建立伪素数表的工作,我们需要改进素性判断的算法。
最简单的想法就是,我们刚才只考虑了a=2的情况。对于式子a^(n-1) mod n,取不同的a可能导致不同的结果。一个合数可能在a=2时通过了测试,但a=3时的计算结果却排除了素数的可能。于是,人们扩展了伪素数的定义,称满足a^(n-1) mod n = 1的合数n叫做以a为底的伪素数(pseudoprime to base a)。前10亿个自然数中同时以2和3为底的伪素数只有1272个,这个数目不到刚才的1/4。这告诉我们如果同时验证a=2和a=3两种情况,算法出错的概率降到了0.000025。容易想到,选择用来测试的a越多,算法越准确。通常我们的做法是,随机选择若干个小于待测数的正整数作为底数a进行若干次测试,只要有一次没有通过测试就立即把这个数扔回合数的世界。这就是Fermat素性测试。
人们自然会想,如果考虑了所有小于n的底数a,出错的概率是否就可以降到0呢?没想到的是,居然就有这样的合数,它可以通过所有a的测试(这个说法不准确,详见我在地核楼层的回复)。Carmichael第一个发现这样极端的伪素数,他把它们称作Carmichael数。你一定会以为这样的数一定很大。错。第一个Carmichael数小得惊人,仅仅是一个三位数,561。前10亿个自然数中Carmichael数也有600个之多。Carmichael数的存在说明,我们还需要继续加强素性判断的算法。
Miller和Rabin两个人的工作让Fermat素性测试迈出了革命性的一步,建立了传说中的Miller-Rabin素性测试算法。新的测试基于下面的定理:如果p是素数,x是小于p的正整数,且x^2 mod p = 1,那么要么x=1,要么x=p-1。这是显然的,因为x^2 mod p = 1相当于p能整除x^2-1,也即p能整除(x+1)(x-1)。由于p是素数,那么只可能是x-1能被p整除(此时x=1)或x+1能被p整除(此时x=p-1)。
我们下面来演示一下上面的定理如何应用在Fermat素性测试上。前面说过341可以通过以2为底的Fermat测试,因为2^340 mod 341=1。如果341真是素数的话,那么2^170 mod 341只可能是1或340;当算得2^170 mod 341确实等于1时,我们可以继续查看2^85除以341的结果。我们发现,2^85 mod 341=32,这一结果摘掉了341头上的素数皇冠,面具后面真实的嘴脸显现了出来,想假扮素数和我的素MM交往的企图暴露了出来。
这就是Miller-Rabin素性测试的方法。不断地提取指数n-1中的因子2,把n-1表示成d*2^r(其中d是一个奇数)。那么我们需要计算的东西就变成了a的d*2^r次方除以n的余数。于是,a^(d * 2^(r-1))要么等于1,要么等于n-1。如果a^(d * 2^(r-1))等于1,定理继续适用于a^(d * 2^(r-2)),这样不断开方开下去,直到对于某个i满足a^(d * 2^i) mod n = n-1或者最后指数中的2用完了得到的a^d mod n=1或n-1。这样,Fermat小定理加强为如下形式:
尽可能提取因子2,把n-1表示成d*2^r,如果n是一个素数,那么或者a^d mod n=1,或者存在某个i使得a^(d*2^i) mod n=n-1 ( 0<=i<r ) (注意i可以等于0,这就把a^d mod n=n-1的情况统一到后面去了)
Miller-Rabin素性测试同样是不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。
Miller-Rabin算法的代码也非常简单:计算d和r的值(可以用位运算加速),然后二分计算a^d mod n的值,最后把它平方r次。程序的代码比想像中的更简单,我写一份放在下边。虽然我已经转C了,但我相信还有很多人看不懂C语言。我再写一次Pascal吧。函数IsPrime返回对于特定的底数a,n是否是能通过测试。如果函数返回False,那说明n不是素数;如果函数返回True,那么n极有可能是素数。注意这个代码的数据范围限制在longint,你很可能需要把它们改成int64或高精度计算。
function pow( a, d, n:longint ):longint;
begin
if d=0 then exit(1)
else if d=1 then exit(a)
else if d and 1=0 then exit( pow( a*a mod n, d div 2, n) mod n)
else exit( (pow( a*a mod n, d div 2, n) * a) mod n);
end;
function IsPrime( a,n:longint ):boolean;
var
d,t:longint;
begin
if n=2 then exit(true);
if (n=1) or (n and 1=0) then exit(false);
d:=n-1;
while d and 1=0 do d:=d shr 1;
t:=pow( a, d, n );
while ( d<>n-1 ) and ( t<>1 ) and ( t<>n-1 ) do
begin
t:=(t * t)mod n;
d:=d shl 1;
end;
exit( (t=n-1) or (d and 1=1) );
end;
对于大数的素性判断,目前Miller-Rabin算法应用最广泛。一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。比如,如果被测数小于4 759 123 141,那么只需要测试三个底数2, 7和61就足够了。当然,你测试的越多,正确的范围肯定也越大。如果你每次都用前7个素数(2, 3, 5, 7, 11, 13和17)进行测试,所有不超过341 550 071 728 320的数都是正确的。如果选用2, 3, 7, 61和24251作为底数,那么10^16内唯一的强伪素数为46 856 248 255 981。这样的一些结论使得Miller-Rabin算法在OI中非常实用。通常认为,Miller-Rabin素性测试的正确率可以令人接受,随机选取k个底数进行测试算法的失误率大概为4^(-k)。
Miller-Rabin算法是一个RP算法。RP是时间复杂度的一种,主要针对判定性问题。一个算法是RP算法表明它可以在多项式的时间里完成,对于答案为否定的情形能够准确做出判断,但同时它也有可能把对的判成错的(错误概率不能超过1/2)。RP算法是基于随机化的,因此多次运行该算法可以降低错误率。还有其它的素性测试算法也是概率型的,比如Solovay-Strassen算法。另外一些素性测试算法则需要预先知道一些辅助信息(比如n-1的质因子),或者需要待测数满足一些条件(比如待测数必须是2^n-1的形式)。前几年AKS算法轰动世界,它是第一个多项式的、确定的、无需其它条件的素性判断算法。当时一篇论文发表出来,题目就叫PRIMES is in P,然后整个世界都疯了,我们班有几个MM那天还来了初潮。算法主要基于下面的事实:n是一个素数当且仅当(x-a)^n≡(x^n-a) (mod n)。注意这个x是多项式中的未知数,等式两边各是一个多项式。举个例子来说,当a=1时命题等价于如下结论:当n是素数时,杨辉三角的第n+1行除两头的1以外其它的数都能被n整除
----------------------------------------------------------------------------------------------------------------------------------
多个字符串排序问题
下面第一种是只用一个指针数组实现的形式
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char *p,*ss[5];
int i,j;
for(i=0;i<5;i++)//输入
{
ss[i]=(char *)malloc(80);
memset(ss[i],'\0',80);
scanf("%s",ss[i]);
}
printf("交换前:\n");//打印
for(i=0;i<5;i++)
printf("%s ",ss[i]);
printf("\n");
for(i=0;i<4;i++)//比较交换
for(j=i+1;j<5;j++)
if(strcmp(ss[i],ss[j])>0)
{
p=ss[i];
ss[i]=ss[j];
ss[j]=p;
}
printf("交换后:\n");//打印
for(i=0;i<5;i++)
printf("%s ",ss[i]);
printf("\n");
return 0;
}
第二种是只用一个二维数组实现,那么比较的时候就不能用strcmp,因为二维数组的每一维都是一个数组的首地址,是一个常量,不能改变,要循环比较二维数组每一维的每个字符元素,交换的时候也是每个字符元素进行交换的
如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char temp,*p,s[5][80];
int i,j,k;
for(i=0;i<5;i++)//输入
{
scanf("%s",s[i]);
printf("%s\n",s[i]);
}
printf("交换前:\n");//打印
for(i=0;i<5;i++)
printf("%s ",s[i]);
printf("\n");
for(i=0;i<4;i++)//比较交换
for(j=i+1;j<5;j++)
{
k=0;
while(s[i][k]==s[j][k])
k++;
if(s[i][k]>s[j][k])
{
for(k=0;k<80;k++)
{
temp=s[i][k];
s[i][k]=s[j][k];
s[j][k]=temp;
}
}
}
printf("交换后:\n");//打印
for(i=0;i<5;i++)
printf("%s ",s[i]);
printf("\n");
return 0;
}
---------------------------------------------------------------------------------------------------------------------
第三种是用一个二维数组和一个指针数组实现的,向二维数组输入字符串后,用指针数组的每个指针元素指向二维数组的每一维,才能strcmp比较
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char *p,s[5][80],*ss[5];
int i,j;
for(i=0;i<5;i++)//输入
scanf("%s",s[i]);
for(i=0;i<5;i++)//赋值
ss[i]=s[i];
printf("交换前:\n");//打印
for(i=0;i<5;i++)
printf("%s ",ss[i]);
printf("\n");
for(i=0;i<4;i++)//比较交换
for(j=i+1;j<5;j++)
if(strcmp(ss[i],ss[j])>0)
{
p=ss[i];
ss[i]=ss[j];
ss[j]=p;
}
printf("交换后:\n");//打印
for(i=0;i<5;i++)
printf("%s ",ss[i]);
printf("\n");
return 0;
}
**********************************************************************************************************************************
关于指针方面
----------------------------------------------------------------------------------------------------------------------------------
指针的各种定义你清楚吗?
int a;
定义一个整形数
int *a;
定义一个指向整型数的指针
int **a;
定义一个指向指针的指针,它指向的指针是一个指向整形数的指针
int a[10];
定义一个又是个整形数的数组
int *a[10];
定义一个又是个指针的数组,该指针是一个指向整形数的指针
int (*a)[10];
定义一个指向有10个整形数数组的指针
int (*a)(int);
定义一个指向函数的指针,该函数有一个整形参数并返回一个整形数
int (*a[10])(int);
定义一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整形数
--------------------------------------------------------------------------------------------------------------------------------------
刚接触指针时
程序代码:
int a = 0;
int *pa = NULL;
pa = &a;
*pa = 5;
printf("*(%p)=%d\n", pa, *(pa));
打印指针的地址,打印指针的值
可是指针永远不会这么简单!它和很多东西都紧密的联系在一起的!
注意 以下的很多代码是错误的 千万不要把错的学会了
指针和静态存储区域的关系
程序代码:
void fun()
{
char *p = "hello";
p[4] = '\0'; //WRONG
}
上面代码中,"hello"存放在静态存储区里,静态存储区存放的值是不能改变的,除非使用特殊手段或改编译参数
正确的是
程序代码:
如果p为全局变量,那么"指hello!"就分配在静态存储区里,不能被修改
void fun()
{
char p[] = "hello";
p[4] = '\0';
}
这里相当于[]内会自动加一个数来确定这个字符串的长度,这是字符数组,是在栈上分配内存,所以可以改
指针和栈的关系
程序代码:
char *func()
{
char p[] = "hello";
return p; //WRONG
}
这里,由于函数在退出的时候函数的栈就销毁了,局部变量分配的内存也就被收回了,所以,返回的指针指向一个已经销毁内存的地址也是无意义的
指针和动态内存分配的关系
程序代码:
int *p = malloc(sizeof(int));
...
p = NULL; //这样没有释放内存块 //WRONG
释放的正确方法是free(p)
当然,在指针释放后将指针赋值为NULL是个好习惯,可以减少错误的发生,而且在使用指针的时候可以判断一下指针是否是NULL,但这只能减少,无法避免 这是C的内存管理机制决定的 该发生的还是会发生
指针和“变量交换”的关系
程序代码:
void swap(int *a, int *b)
{
int *t = NULL;
t = a;
a = b;
b = t;
}
这里只不过在函数内部交换了两个指针里存放的地址,而不是交换两个指针变量里的地址所指向的值,过程如下:
[attach]66764[/attach][attach]66765[/attach]所以这个函数看似地址传递,但其实还是值传递,只不过传递的是值是地址而已,一旦这个函数返回了,函数的堆栈销毁了,也是没有意义的返回
正确的是
程序代码:
void swap(int *a, int *b)
{
int t = 0;
t = *a;
*a = *b;
*b = t;
}
这里交换两个指针变量中存放的地址中的值,就交换了两个实参的值
指针和const常量的关系
void fun()
{
char c='c';
char *const p1='h';
char const *p2='h';
const char *p3='h';
const char *const p4='h';
p1=&c; //WRONG
p2=&c;
p3=&c;
p4=&c; //WRONG
*p1=c;
*p2=c; //WRONG
*p3=c; //WRONG
*p4=c; //WRONG
}
char *const p1;
指针指向的地址不可改变,指向地址中的值可变
char const *p2;
指针指向地址中的值不可改变,指向的地址可变
const char *p3;
同上
const char *const p4;
指针指向的地址和地址中的值都不可改变
指针与二维数组的关系:
二维数组的传递分为下面几种:
第一种:
fun(int p[2][3]) {} //这种是将形参的各维数都标清楚,实现实参数组元素与形参数组元素一一对应
main()
{ int num[2][3]={1,2,3,4,5,6}; fun(num); }
第二种:
fun(int p[][3]) {} //这种是将一维大小省略(行数省略),二维大小标清楚,系统自动匹配个元素的对应关系,达到一一对应
main()
{ int num[2][3]={1,2,3,4,5,6}; fun(num); }
第三种:
注:可省略const
fun(const int m,const int n,int p[m][n]) {} //这种是通过传递各维大小来确定形参数组大小(p不能放在m,n前面编译器支持C99,自己想),达到实参数组元素与形参数组元素一一对应
main()
{ int num[2][3]={1,2,3,4,5,6}; fun(2,3,num); }
以上三种都可以
但就是不能fun(int **p),因为二维指针在有些编译器下是不能当做数组来用的,即使能当做数组来用,那么数组各位都不确定,也就是数组大小不确定,这种声明是错误的,必须至少确定第二维的大小
***************************************************************************************************************************************
关于动态申请内存方面
-----------------------------------------------------------------------------------------------------------------------------------------
一级指针动态申请内存
struct flight *p=(struct flight *)malloc(sizeof(struct flight));
p是指针变量,占sizeof(int)个字节(int型字节大小和指针的字节相等),存放p指向的内存块的首地址,这块内存块是sizeof(struct flight)字节大小,因此malloc在堆上分配sizeof(struct flight)字节大小,也就是p指向的内存块的大小,即malloc(sizeof(struct flight))
二级指针动态申请内存
struct flight **p=(struct flight** )malloc(sizeof(struct flight*)*flag);
p同样是指针,占sizeof(int)个字节(int型字节大小和指针的字节相等),存放p指向的内存块的首地址,这块内存块是sizeof(int)*flag字节大小,这块内存块中每sizeof(int)字节又存放一个sizeof(struct flight)大小的内存块的首地址,因此malloc在堆上分配sizeof(int)*flag字节大小,也就是p指向的内存块的大小,即malloc(sizeof(struct flight*)*flag)
*****************************************************************************************************************************************
小知识点
伪指令#pragma pack (n)是改变系统字节对齐方式,以n字节对齐
伪指令#pragma pack ()是取消自定义字节对齐方式,恢复系统默认字节对齐方式
system("pause");作用
答:系统的暂停程序,按任意键继续,屏幕会打印,"按任意键继续。。。。。" 省去了使用getchar();
memset ,memcpy 和strcpy 的根本区别?
memset用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为' '或'\0';
例:char a[100];memset(a, '\0', sizeof(a));
memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;
例:char a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a),会造成b的内存地址溢出。
strcpy就只能拷贝字符串了,它遇到'\0'就结束拷贝;
例:char a[100],b[50];strcpy(a,b);如用strcpy(b,a),要注意a中的字符串长度(第一个'\0'之前)是否超过50位,如超过,则会造成b的内存地址溢出。
内存拷贝:
内存拷贝其实就是将一个内存中的值复制到一个内存中,这两个内存块可以相同可以不同
简单的内存拷贝如char a='q',b;b=a;这也是一种内存拷贝,只不过没有用到函数
在主函数传值给形参时也用到内存拷贝,如调用函数中int a=5,b=3;fun(a,b);被调函数中int fun(int a,int b);
还有一些内存拷贝函数如下:
原型:extern char *strcpy(char *dest,char *src);
用法:#include <string.h> 功能:把src所指由NULL结束的字符串复制到dest所指的数组中。
说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。 返回指向dest的指针。
原型:extern void *memcpy(void *dest, void *src, unsigned int count);
用法:#include <string.h> 功能:由src所指内存区域复制count个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
原型:extern void *memmove(void *dest, const void *src, unsigned int count);
用法:#include <string.h> 功能:由src所指内存区域复制count个字节到dest所指内存区域。
说明:src和dest所指内存区域可以重叠,但复制后src内容会被更改。函数返回指向dest的指针。
原型:extern void *memccpy(void *dest, void *src, unsigned char ch, unsigned int count);
用法:#include <string.h>
功能:由src所指内存区域复制不多于count个字节到dest所指内存区域,如果遇到字符ch则停止复制。
说明:返回指向字符ch后的第一个字符的指针,如果src前n个字节中不存在ch则返回NULL。ch被复制。
注意:内存重叠问题。
unsigned如果省略后面的关键字的情况,大多数编译器会默认是unsigned int类型
*********************************************************************************************************************************************************
[ 本帖最后由 小小战士 于 2012-12-17 14:38 编辑 ]