C语言指针重学

学习要纲:建议掌握
gdb调试(b ,d ,fin ,bt ,print ,awatch ,up ,down ,set pretty等)
SourceInsight软件看代码(全局搜索 文件搜索等)
git如何调取分支合并(git branch,git blame,git log,git pull,git reset --hard等)
等内容,下面是对于指针的一个重新学习.
C语言的指针:

  • 使得程序简单、紧凑、高效
  • 有效地表示复杂数据结构
  • 动态分配内存
  • 得到多于一个的函数返回值

1.指针的基本用法

A.地址和变量:在计算机的内存中,每一个字节单元,都有一个编号,称为地址。

在C语言之中,内存单元的地址称为指针,专门用来存放地址的变量称为指针变量

1byte = 8 bits

<存储类型> <数据类型> * <指针变量名> = <地址量>;

使用*取出相应的值,*p / *(&a)

B.指针的赋值运算值的是通过赋值运算符指向地址变量送一个地址值

注意:一般在Centos之中指针的大小都为四个字节

0x 00 00 00 00 - 0x FF FF FF FF :四个字节 2^(4*8)-1

2.指针的运算

指针运算是指以指针变量所存放的地址量作为运算量而进行的运算

p + n:指针向着大地址方向进行移动,表示p + sizeof(p 的类型) * n,为地址量
px -py:指数据的个数

3.指针与数组

4.指针与二维数组

5.指针与字符串

A.字符指针

B.注意点
初始化字符指针是把内存中字符串的首地址赋予指针,而不是把该字符串复制到指针中

char str[] = "Hello World";
char *p = str;
char ch1[] = "Hello World";
char ch2[] = "Hello World";

注意:ch1和ch2虽然是内容相同的,但是地址是不同的.
c.在C编程过程中,当一个字符指针指向一个字符串常量时,不能够修改指针指向的对象的值

char *p = "Hello World";
char *p = 'H';//错误,字符指针常量是不允许改变的

这个地方是一个易错点:

可以见到字符串常量的地址是相同的,且输出字符指针的方式是直接打印其值.

给出一个错误案例:

想要把第一个字母变成小写,会出现Segmentation fault报错,说明内存访问过程出现了问题.

6.二级指针

A.

B.

C.稍微进阶版---此处需要结合指针与字符串进行学习
 

#include <stdio.h>

int main()
{
	char *s[] = { "apple", "pear", "potato" };
	char ** p;
	int i, n;

	i = 0;
	n = sizeof(s) / sizeof(char *);
	p = &s[0];

	while (i < n)
	{
		printf("%s %s\n",s[i],*(p+i));
		i++;
	}

	getchar();
	return 0;

}

7.void指针和const修饰符

void指针是一种不确定数据类型的指针变量,在没有进行强制类型转换之前,不能进行任何指针的算数运算.
A.一般形式:void *<指针变量名称>

可以见到*p 和 *q是非法间接寻址,因为不知道如何取类型的地址大小.
当将* p变为* (int *)p即可

注意:void指针在进行使用的时候必须要进行强制转换.

B.对于void指针在没有进行强制类型转换之前是不能够进行任何的算数运算


 

C.const变量

常量化变量值
const <数据类型> 变量名 = [<表达式>];

常量化变量是死了使得变量的值不被修改,变量有const修饰时,想要用指针间接访问变量,指针也要有const修饰.

①const int *p ------ 

不能够通过指针改变目标值.

② int * const p ------ 

只能够将上面的赋值过程变为int const *q = &m,也就是q的地址不能够修改

也就是const修饰谁,谁可以不可以修改

③const int * const r = &m;
任何值都不能更改

D.main函数是否可以带参数,涉及到const指针

int main(int argc,const char * argc[])注意这个地方是针对于在Centos系统下想起来一个命令
gcc -std=c++11 ys.cpp -o a
注意:这个地方的
int argc = 1
const char * argc[] = {"./a","192.168.6.118"}

8.函数参数的用法

A.函数之间参数的传递
①全局变量传递参数

形参的地址不一样

②复制传递

调用函数将实参传递给被调用函数,被调用函数将创建同类型的形参并用实参进行初始化.

形参是新开辟的存储空间,在函数中改变形参的值,不会影响到实参.

③地址传递
按照地址进行传递,实参为变量的地址,形参是同类型的指针;
被调用函数中对形参的操作,将直接改变实参的值(被调用函数对指针目标的操作相当于对实参本身的操作)

编写一个函数,统计字符串中小写字母的个数,并把字符串中的小写字母转化成大写字母
a.统计字符串中小写字母的个数

注意:当然这个功能是非常容易进行实现的,需要进行注意的是这个地方的形参为什么是定义的为const char *p,这样定义的话就是不会改变其*p的内容,和前面的内容进行了串联.

b.将小写字母转换为大写字母

这个地方需要进行注意空格的ascll为32.

B.数组传递参数

三种方式:

①全局数组传递方式

②复制传递方式
例题:编写函数,计算一个一维整型数组的所有元素的和
实参为数组的指针,形参为数组名(本质是一个指针变量)

对于上面那句话的解读是,比如说一个形参 int p[],相当于int *p

③地址传递方式
实参为数组的指针,,形参为同类型的指针
例题:去掉字符串里面的空格

9.指针函数

A.指针函数是指一个函数的返回值为地址量的函数

指针函数的定义一般形式如下:

<数据类型> * <函数名称>(<参数说明>)
{

}

举个例子:请你说出下面代码的问题

这个地方的str[20]是局部变量,是在栈上的,返回不了了
结果是乱码了,如下所示:

原因是在于:之前租的房子,现在进不去了,所以打印出来的是乱码看不见.

改进方式: 
①将其变为全局变量 char str[20] 改法看起来怪怪的
②改变为静态变量     static char str[20] ------ 可用
③变成全局字符串常量 char * str = "Hello"; 不能够进行修改
④使用malloc的方式

B.
 

#include <stdio.h>

// 定义一个返回int类型指针的函数
int* find_max(int* a, int* b) {
    return (*a > *b) ? a : b;
}

int main() {
    int x = 10, y = 20;
    
    // 调用指针函数find_max,返回x和y中较大的那个数的地址
    int* max_ptr = find_max(&x, &y);
    
    printf("The max value is: %d\n", *max_ptr);
    
    return 0;
}

10.函数指针

函数指针用来存放函数的地址,这个地址是一个函数的入口地址
函数名代表了函数的入口地址
函数指针变量说明的一般形式如下:
<数据类型> (*<函数指针名称>) (<参数说明列表>);

#include <stdio.h>

// 定义一个返回int类型,接受两个int参数的函数
int add(int a, int b) {
	return a + b;
}

int main() {
	// 声明一个函数指针,指向接受两个int参数并返回int类型的函数
	int(*func_ptr)(int, int);

	// 将函数指针指向add函数
	func_ptr = add;

	// 通过函数指针调用add函数
	printf("The result is: %d\n", (*func_ptr)(3, 4));
	getchar();
	return 0;
}

函数指针的声明方式是先声明返回类型,然后是函数指针变量名,再跟随函数的参数类型。

这个地方可以结合指针数组一起使用,相应的指针数组如下所示:

#include <iostream>
using namespace std;

int main() {
    // 定义一个指针数组,能够存储5个int类型的指针
    int* ptrArray[5];
    
    // 定义一些整数
    int a = 10, b = 20, c = 30, d = 40, e = 50;
    
    // 将每个整数的地址赋值给指针数组中的元素
    ptrArray[0] = &a;
    ptrArray[1] = &b;
    ptrArray[2] = &c;
    ptrArray[3] = &d;
    ptrArray[4] = &e;
    
    // 通过指针数组输出各个整数的值
    for (int i = 0; i < 5; i++) {
        cout << "Value of ptrArray[" << i << "]: " << *ptrArray[i] << endl;
    }

    return 0;
}

--------------------------------------------------------------------------------------------------(捎带一提)

11.条件编译

1.根据宏判断是否进行
#define <macro_0828> 1(可省略)

#ifdef <macro_0828>
........
#else
......
#endif

12.结构体指针

几种定义形式:

第一种:

struct Date {
    int day;
    int month;
    int year;
};

第二种:

struct Date {
    int day;
    int month;
    int year;
} ys_0828;

第三种:

struct {
    int day;
    int month;
    int year;
} ys_0828;

第四种:

struct {
    int day;
    int month;
    int year;
} ys_0828 = {2024,8,28};

第1、2、4种的声明 struct Data use; 内部变量的访问使用.

结构体指针:struct student *p;
访问成员变量的方法:(*p).name或者p->name

13.共用体

不同数据类型的数据可以使用共同的存储区域,这种数据构造类型叫做共用体,形似一个结构体。定义形式

union dy(共用体名称)
{
    int i;
    char c;
    float f;
}

共用体类型的union dy,由三个成员组成,这三个成员使用共同的存储空间,按照最大的float进行存储(与struct不同)。 定义过程 union dy use;
其中三个成员的地址是相同的,在完成赋值以后,只有f是有效的,别的int i、char c都是没有意义的,比如说f = 0x12345678,则地址从小到大为0x78 0x56 0x34 0x12,是一个小端存储的过程。

14.typedef

在C语言之中,允许使用关键字typedef定义新的数据类型,其语法如下:
typedef  <已有数据类型> <新数据类型>
①typedef int INTEGEER
②结构体-----经常看到

typedef struct _node_
{
    int data;
    struct _node_ *next;
}listnode, *linklist;

其中listnode等价于struct _node_,而linklist等价于struct _node_ *

15.内存的空间【面试的时候背了好多遍,老生常谈】

A.内存空间的四个区
①代码区
②全局变量与静态变量区 (字符串常量:
const char MY_CONSTANT_STRING[] = "Hello, World!";)
在大多数现代Linux系统上,用户空间程序的虚拟地址空间通常为4GB(在32位系统上)或更大(在64位系统上)。查看指令:ulimit -v
全局变量区(即数据段和BSS段)通常会受到以下因素的影响:
数据段:存储已初始化的全局变量和静态变量。
BSS段:存储未初始化的全局变量和静态变量。这部分内存在程序加载时被自动初始化为零。
③栈区:局部变量 ulimit -a 查看栈区的内存

④动态存储区 void * malloc(size_t num)/void free(void *p)的使用
malloc函数本身并不识别需要申请的内存类型,与申请的内存总字节数有关
malloc申请到的是一块连续的内存,申请不到返回null
malloc返回值是void *类型,所以在进行调用的时候需要进行显式转换
free的参数如果是null,没有任何效果

#include <stdlib.h>//malloc
#include <stdio.h>
int main()
{
	char *p;
	p = (char *)malloc(10 * sizeof(char));
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	free(p);

	return 0;
}

14.静态库的制作

A.库是一个二进制文件,包含的代码可被程序直接调用 目录:/lib/
库是编译好的,可以进行复用的代码

B.静态库:编译时把静态库中相关代码复制到可执行文件中
a.程序中已包含代码,运行时不再需要静态库
b.程序运行时无需加载库,运行速度更快
c.占用更多的磁盘和内存空间
d.静态库升级之后,程序需要重新编译链接

C.静态库中函数的功能和接口
 

15.动态库的制作

16.最近碰到的,顺便一提:size_t  static_cast  long strtol auto decltype template

A.size_t
size_t
是一个无符号类型,用于表示对象的大小或数组的索引。在 C 和 C++ 标准库中,size_t 的具体定义是由实现来决定的,它通常被定义为能够表示目标平台上最大的可寻址内存块的大小的类型。
在32位系统上,size_t 通常是 4 字节(32位),可以表示的值范围是 0 到 4,294,967,295。在64位系统上,size_t 通常是 8 字节(64位),可以表示的值范围是 0 到18,446,744,073,709,551,615。

有几个原因使得 size_t 在特定场合比 int 更合适:
①表示范围: int 是有符号整数,通常其值范围是 -2,147,483,648 到 2,147,483,647(在32位系统上),这意味着它只能表示正数的一半范围。而 size_t 是无符号的,因此可以表示更大的正整数范围。这对于表示内存大小或数组长度等不会为负的值特别有用。
②跨平台兼容性: size_t 的大小根据目标平台自动调整。例如,在 32 位系统上,它是 32 位的,而在 64 位系统上,它是 64 位的。这样做可以确保在不同平台上正确地表示内存大小和数组索引。而 int 的大小在某些平台上可能会造成问题,因为它的大小是固定的。
③标准库的要求: 很多标准库函数使用 size_t 作为参数类型,例如 sizeof 操作符返回 size_t 类型的值。为了与这些库函数保持一致,使用 size_t 是一个好的实践。
④防止逻辑错误: 由于 size_t 是无符号类型,它不能表示负数。这有助于防止在处理如数组索引和内存大小时的逻辑错误。例如,如果你用 int 作为数组索引的类型,可能会意外地进行负索引操作,从而导致潜在的内存错误。

B.static_cast

static_cast 是 C++ 中的一种类型转换运算符,用于在已知可以安全转换的情况下,将一个对象显式地转换为另一种类型。与 C 风格的强制类型转换相比,static_cast 更加安全和明确,编译器可以在编译时检查转换的合法性。
①基本类型的转换

int a = 10;
float b = static_cast<float>(a); // 将 int 转换为 float

②指针类型之间的转换

Derived derived;
Base* basePtr = static_cast<Base*>(&derived); // 将派生类指针转换为基类指针

③枚举类型之间的转换 枚举类型的访问是使用::

enum class Color { Red, Green, Blue };

Color color = Color::Red;
int colorValue = static_cast<int>(color); // 枚举类型转换为 int

④它是禁止进行隐式转换的

C.long类型(一定是记忆错乱了,没想起过这个类型)

long 的大小在不同平台上可能不同。例如,在 Windows 上,无论是 32 位还是 64 位,long 都是 4 字节;而在 Linux 上,long 在 64 位系统上是 8 字节。在跨平台编程时需要注意这一点。

D.strtol
该类型在进行使用的过程中,其定义如下所示:
long strtol(const char *str, char **endptr, int base);
str:转换的字符;
endptr: 指向一个字符指针的指针,函数会将未处理的字符串部分的指针存储在此位置。如果不关心未处理的部分,可以传递 NULL;
base
: 指定进制(2 到 36)。如果 base 为 0,函数会根据字符串的前缀自动检测进制(例如,0x 表示16进制,0表示8进制,其余表示10进制)。

返回值:
成功时,返回转换后的 long 类型值;
如果没有有效的转换,返回值为 0;
如果转换得到的值超出 long 类型的表示范围,返回 LONG_MAXLONG_MIN,并设置 errnoERANGE

    const char *str = "12345";
    char *endptr;
    long value = strtol(str, &endptr, 10);

    if (*endptr == '\0') {
        printf("Converted value: %ld\n", value);
    } else {
        printf("Partial conversion: %ld, Unconverted part: %s\n", value, endptr);
    }

E.auto

记着能不用就不要用,自动判断类型

F.decltype
decltype: 用于推导任意表达式的类型,而不对其进行求值

    int a = 3;
    auto b = a;        // b 的类型是 int
    decltype(a) c = a; // c 的类型也是 int

    const int& d = a;
    auto e = d;        // e 的类型是 int(const 和引用被忽略)
    decltype(d) f = d; // f 的类型是 const int&

G.template
①函数模板类

#include <iostream>

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << "Max of 3 and 7: " << max(3, 7) << std::endl;         // int 类型
    return 0;
}

②类模板

#include <iostream>

template <typename T1, typename T2>
class Pair {
public:
    Pair(T1 first, T2 second) : first_(first), second_(second) {}

    T1 getFirst() const { return first_; }
    T2 getSecond() const { return second_; }

private:
    T1 first_;
    T2 second_;
};

int main() {
    Pair<int, double> p1(42, 3.14);
    std::cout << "First: " << p1.getFirst() << ", Second: " << p1.getSecond() << std::endl;


    return 0;
}

再深的地方等遇到再看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值