指针与字符串

指针

C语言程序在内存中的分布 4G的虚拟内存空间 编号:0x0000 0000 - 0xffff ffff
4G = 2^2 * 2^30 byte = 2^32 byte
一个地址只能存储1byte数据
所谓的内存 其实是一个 int 类型的编号 通常显示为十六进制 %p

指针即内存地址

定义变量意味着分配内存 变量其实是代表着那一片内存里的数据
操作变量实际上是操作内存里的数据
所有的变量(除了register)其实都可以通过&来获取该变量在内存中的存储位置

如果确定了一个内存地址,可以通过*来获取该内存区域里的数据

& 取址运算符 变量(除了register)
* 取值运算符 确定地址的类型 涉及一个取多少个字节数据的问题
&* *& 相互抵消 过程正好相反

指针变量: 存储内存地址的变量
指针变量的定义:
数据类型 *标识符; * 在定义时表示是指针类型
int p; p是int类型
int p1,p2; p1是int类型 p2是int类型

指针变量初始化:
NULL 空指针 0x0000 0000 假
一般用 != NULL 来判断一个指针变量是否可以取运算
野指针 未初始化的指针变量(指向不确定 指针变量中存储的值不确定)
野指针非常危险 编译时不会检查
程序可能正常 可能逻辑错误 可能崩溃
万能指针 void *
任何类型的指针都可以隐式转换成void *
void
类型的指针都可以隐式转换为任何类型的指针
除上述情况以外,任何指针类型之间的转换需要 强制类型转换
(目标类型)源变量
不能对万能指针进行取*运算

指针变量的操作:
指针变量 = 指针(内存地址 &变量)
*指针变量 取内存地址的值

指针变量加减整数±
指针变量+1 指针变量的值加了一个内存地址中数据类型的单位长度

指针的意义:

在函数内部修的数据调用者可见
写一个函数交换两个整数的值:

	//错误1: 交换形参的值  不影响实参
		void swap(int a,int b){
			int tmp = a;
			a = b;
			b = tmp;
		}
		//错误2: 交换了形参pa和pb的值 
		void swap1(int *pa,int *pb){
			int *pt;//int *pt = pa;
			pt = pa;
			pa = pb;
			pb = pt;
		}
		//错误3: 野指针
		void swap2(int *pa,int *pb){
			int *pt;
			*pt = *pa;
			*pa = *pb;
			*pb = *pt;
		}
		
		void swap3(int *pa,int *pb){
			int t = 0;
			int *pt = &t;
			*pt = *pa;
			*pa = *pb;
			*pb = *pt;
		}
		
		void swap4(int *pa,int *pb){
			int t = 0;
			t = *pa;
			*pa = *pb;
			*pb = t;
		}
		void swap5(int *pa,int *pb){
			*pa ^= *pb;
			*pb ^= *pa;
			*pa ^= *Pb;
		}
		

指针与一维数组(数组是一片连续的内存空间):
[] 数组下标运算符 *()
arr[i] --> *(arr+i)
*(arr+i) (i+arr) i[arr]
int arr[10] = {0};
arr == &arr[0] 一维数组名即首元素地址
&arr[0] == &
(arr+0) == arr

数组作为函数参数传递时:
传递的其实是数组首元素的地址
void func(int arr[长度],int n){
sizeof(arr) == 4/8
}
void func(int arr,int n){
在函数中修改数组元素的值会影响实参数组的值
}
sizeof(指针) == 4/8
高级指针
指针: 即内存地址
指针变量: 保存内存地址的变量
内存地址: 0-4G的整数 %p 十六进制 虚拟内存
* 取内存地址中的数据 取
之前需要明确多少个字节的数据
& 取变量的内存地址
NULL 0x0
野指针
void * 万能指针 可以隐式转换为任意类型的指针 任意类型的指针也可以隐式转换为void*
指针的算术运算:
指针+1 偏移单位长度的地址

二级指针:
一级指针变量的地址
数组与指针:
	int arr[5] = {1,2,3,4,5};
	int *p = &arr[0];  //&arr[0] == &*(arr+0) == arr
	arr+2  == &*(arr+2)  == &arr[2]
	arr == &arr[0]    
	&arr   与arr,&arr[0]都是同一个地址 但是类型不一样 
		arr,&arr[0]  都是指首元素的地址 +1  偏移一个元素的地址
		&arr 是数组的地址  +1  偏移了整个数组的地址
		&arr 其实是数组指针
	int (*parr)[5] = &arr;
		parr指向一个数组  数组长度为5
		parr+1   偏移了一个一维数组的长度
		parr[0]  ==  *(parr+0)  ==  *parr  == *&arr == arr
		parr[0][0]

int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
&arr[0][0]  首元素地址
&arr[0][0] == &*(arr[0]+0) == arr[0]
int *p = arr[0];// &arr[0][0]
p = arr[2];//&*(arr[2]+0)  ==  &arr[2][0]
p = &arr[1][2];

int (*parr)[4] = &arr[0];// &arr[0] == &*(arr+0) == arr
parr = arr;
*parr ==  *arr  ==  *&arr[0]  == arr[0] == &*(arr[0]+0) == &arr[0][0]
**parr ==  arr[0][0]
int (*pta)[3][4] = &arr;


int arr[3][4][5];
int (*pa)[4][5] = arr;
int (*par)[3][4][5] = &arr;

指针数组:  是数组  数组中的元素是指针
int * arr[3];
char* arr[3];

函数名是一个地址  指针
函数指针:  是一个指针  指针指向一个函数
函数指针变量的定义:
	函数返回值类型 (*变量名)(形参列表);// 函数指针变量

指针函数:  是一个函数  函数返回值类型为指针
int * func(int *a){}

int * (*pf)(int *) = func;//函数指针
int a;
pf(&a);//通过函数指针变量 调用函数

typedef 返回值类型 (*类型名)(形参列表);
给函数指针类型取别名;
返回值类型 (*标识符)(形参列表); 定义了一个函数指针变量

4G的内存分布 从低到高:
代码段:

程序运行时加载程序指令到内存中形成4G虚拟内存镜像
存储代码指令 字符串字面值 代码段的内容不能修改 一旦修改必然核心已转储(段错误)

全局静态区-数据段

已初始化的全局变量和静态变量

全局静态区-bss段

未初始化的全局变量和静态变量 全部自动清0

堆(动态内存)

从小到大使用
程序员手动申请 手动释放
堆栈缓冲区(加载的动态库)
使堆栈数据不重叠

从大到小使用
局部变量 形参 块变量 调用函数时的内存开销
程序自己维护 不需要程序员自己操心
自动分配 自动回收
命令行参数和环境列表

C语言程序出现段错误的原因:

1.对非法的内存访问(对没有映射到物理内存的地址的访问)
野指针:基本上都是没有映射到物理内存
2.对没有权限修改的内存区域进行访问修改 (修改代码段的内容)

auto static const register volatile extern
auto 自动的 默认的 省略
static 静态的
1.修饰局部变量 静态局部变量
存储在全局数据区 普通的局部变量存储在栈区
生命周期到程序结束 普通局部变量生命周期为函数调用结果
作用域是一样的
2.修饰全局变量 静态全局变量
静态全局变量只能在当前文件中使用 普通的全局变量是可以在其他文件中使用
存储位置和生命周期不变
static修饰的变量 只会 执行一次定义语句(后面再调用自动忽略定义语句)
3.修饰函数
静态函数限定于当前文件使用 如果是.h 导入到各个文件
在其他文件中可以声明同名的函数
const 只读的 常量的
修饰变量 表示只读的 通常用作常量
修饰形参 防止在函数内部对实参进行修改
const int *p;
int const *p;
int * const p;
const int * const p;

register 寄存器变量

请求作为寄存器变量
要求变量宽度必须是 4/8
不能对register变量进行取&

volatile 易变的

volatile修饰的变量的值可能随时发生变化

extern 声明

1.声明在其他文件中定义的函数或者变量
2.全局变量和局部变量同名 extern可以在块语句中导入全局变量

字符串

与指针结合最紧密
C语言中没有字符串这个基础数据类型
在C语言中字符串一串以’\0’为结尾的字符 称为字符串

字符一个字节 内存地址 一个内存地址存储一个字节
在连续的内存上面存储字符 并且认为遇到’\0’为字符串的结束标识
‘\0’ 0 逻辑假

C语言中字符串的存在形式:
1.字符串字面值
“Hello”
“中国牛逼”
存储在代码段(不能修改)
字面值相同的字符串在内存中只会保留一份

2.字符指针
记录字符串只需要记住首地址(第一个字符的地址)即可
char *p = “Hello”;
虽然是一个字符指针 记录一个字符在内存中的地址
但由于字符串是以’\0’为结束标识的,所以记录了第一个字符的地址,其实就是记录了整个字符串
字符指针 指向 字面值字符串
char *p = “Hello”;//指向代码区
char str[] = “Hello”;
char *p = str;//指向栈区

3.字符数组
数组一定要预留一个’\0’的位置 要确保在数组中有’\0’
char str[10] = {‘h’,‘e’,‘l’,‘l’,‘o’};//自动补0 0就是’\0’
char str[10] = “hello”; //方便 把字面值字符串的字符逐一赋值给数组中的元素

char str[] = {'h','e','l','l','o'};//没有'\0'  数组长度为5
			char str[] = "hello";//有'\0'  自动把'\0'赋值到数组中  数组长度为6
			char *p = str;

在输出时如果指定格式为%s 则表示从某个地址开始格式输出字符,然后遇到’\0’为止

字符串:
字面值字符串
字符数组
字符指针 指向 字面值字符串 或者 字符数组

字符串的长度:
strlen 不包括’\0’ 求字符串中字符的个数
size_t strlen(const char *s);
sizeof 求内存大小 所以会包括’\0’
char *strcpy(char *dest,const char *src);//把src字符串的内容拷贝到dest中
char *strncpy(char *dest,const char *src,size_t n);//从src中拷贝n个字符到dest中
char *strcat(char *dest,const char *src);
把src字符串追加到dest后面
char *strncat(char *dest,const char *src,size_t n);

int strcmp(const char *s1,const char *s2);
		int strncmp(const char *s1,const char *s2,size_t n);
		
		//内存拷贝函数
		void *memcpy(void *dest,const void *src,size_t n);
		从src地址开始拷贝n个字节的数据到dest地址处

	char str[10] = "Hello";
	char *p = str;
	char c = *p++;//先取*p运算  然后p=p+1
	c 得到的是 'H'
	p 指向 'e'
	
	char *p = str;
	char c = (*p)++;//先取*p运算 然后  *p = *p+1
	c 得到的是 'H'
	p 指向的是 'Iello'

操作函数:

求字符串长度 字符的个数(不包括’\0’)
size_t strlen(const char *s);
	size_t strlen(const char *s){
		assert(s!=NULL);
		size_t len;
		for(len=0;*(s+len)!='\0';len++);
		return len;
	}
	size_t strlen(const char *s){
		assert(s!=NULL);
		size_t len = 0;
		while(*s++ != '\0'){
			++len;
			//++s;
		}
		return len;
	}
字符串拷贝
char *strcpy(char *dest,const char *src);
	char *strcpy(char *dest,const char *src){
		assert(dest!=NULL && src!=NULL);
		size_t i;
		for(i=0;*(src+i)!='\0';i++){
			*(dest+i) = *(src+i);
		}
		*(dest+i) = '\0';
		return dest;
	}
	char *strcpy(char *dest,const char *src){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while((*dest = *src)!='\0'){
			++dest;
			++src;
		}
		return pdest;
	}
	char *strcpy(char *dest,const char *src){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while((*dest++ = *src++)!='\0');
		return pdest;
	}
拷贝n个字符
char *strncpy(char *dest,const char *src,size_t n);
	char *strncpy(char *dest,const char *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		size_t i=0;
		for(i=0;i<n;i++){
			*(dest+i) = *(src+i);
			if(*(dest+i)=='\0'){
				break;
			}
		}
		return dest;
	}
	
	char *strncpy(char *dest,const char *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while(n>0){
			*dest = *src;
			++dest;
			++src;
			--n;
		}
		return pdest;
	}
	
	char *strncpy(char *dest,const char *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while(n-->0){
			*dest++ = *src++;
		}
		return pdest;
	}
字符串追加
char *strcat(char *dest,const char *src);
	char *strcat(char *dest,const char *src){
		assert(dest!=NULL && src!=NULL);
		size_t len;
		for(len=0;*(dest+len)!='\0';len++);
		size_t i;
		for(i=0;*(src+i)!='\0';i++){
			*(dest+len+i) = *(src+i);
		}
		*(dest+len+i) = '\0';
		return dest;
	}
	
	char *strcat(char *dest,const char *src){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while(*dest != '\0'){
			++dest;
		}
		while(*src!='\0'){
			*dest = *src;
			++dest;
			++src;
		}
		*dest = '\0'
		return pdest;
	}

	char *strcat(char *dest,const char *src){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while(*dest != '\0'){
			++dest;
		}
		//while(*dest++ != '\0'); --dest;
		while((*dest++ = *src++)!='\0');
		return pdest;
	}
	
	char *strncat(char *dest,const char *src,size_t n);
	char *strncat(char *dest,const char *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		size_t len;
		for(len=0;*(dest+len)!='\0';len++);
		size_t i;
		for(i=0;i<n && *(src+i)!='\0';i++){
			*(dest+len+i) = *(src+i);
		}
		*(dest+len+i) = '\0';
		return dest;
	}
	char *strncat(char *dest,const char *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while(*dest != '\0'){
			++dest;
		}
		while(n>0){
			*dest = *src;
			if(*dest == '\0'){
				break;
			}
			++dest;
			++src;
			--n;
		}
		return pdest;
	}
	char *strncat(char *dest,const char *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		while(*dest != '\0'){
			++dest;
		}
		while(n-->0 && (*dest++ = *src++)!='\0');
		return pdest;
	}
字符串比较
	int strcmp(const char *s1,const char *s2);
	int strcmp(const char *s1,const char *s2){
		assert(s1!=NULL && s2!=NULL);
		size_t i;
		for(i=0;*(s1+i)!='\0' && *(s2+i)!='\0' && *(s1+i)==*(s2+i);i++);
		return *(s1+i) - *(s2+i);
	}
	int strcmp(const char *s1,const char *s2){
		assert(s1!=NULL && s2!=NULL);
		while(*s1!='\0' && *s2!='\0' && *s1==*s2){
			++s1;
			++s2;
		}
		return *s1-*s2;
	}

	//比较前n个字符是否相等 如果前面n-1个相等 比较后一个
	int strncmp(const char *s1,const char *s2,size_t n);
	int strncmp(const char *s1,const char *s2,size_t n){
		assert(s!=NULL && s2!=NULL);
		size_t i;
		for(i=0;i<n-1 && *(s1+i)!='\0' && *(s2+i)!='\0' && *(s1+i)==*(s2+i);i++);
		return *(s1+i)-*(s2+i);
	}

	int strncmp(const char *s1,const char *s2,size_t n){
		assert(s!=NULL && s2!=NULL);
		while(n>1 && *s1!='\0' && *s2!='\0' && *s1==*s2){
			--n;
			++s1;
			++s2;
		}
		return *s1-*s2;
	}
内存拷贝函数
void *memcpy(void *dest,const void *src,size_t n);
	void *memcpy(void *dest,const void *src,size_t n){
		assert(dest!=NULL && src!=NULL);
		char *pdest = dest;
		const char *psrc = src;
		while(n>0){
			*pdest++ = *psrc++;
			--n;
		}
		return dest;
	}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值